Compare commits
3 Commits
4133052165
...
9ab9b41588
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ab9b41588 | |||
| 32e5fbe6ac | |||
| 784968521a |
@@ -18,10 +18,6 @@ externalPort = 80
|
||||
localPort = 35345
|
||||
externalPort = 3002
|
||||
|
||||
[[ports]]
|
||||
localPort = 37553
|
||||
externalPort = 4200
|
||||
|
||||
[[ports]]
|
||||
localPort = 39063
|
||||
externalPort = 3001
|
||||
|
||||
+1
-3
@@ -25,8 +25,6 @@ COPY --from=dependencies /tmp/prod_node_modules ./node_modules
|
||||
|
||||
# Copy built application
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/server ./server
|
||||
COPY --from=build /app/shared ./shared
|
||||
COPY --from=build /app/drizzle.config.ts ./
|
||||
COPY --from=build /app/package.json ./
|
||||
|
||||
@@ -42,4 +40,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD node -e "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "server/index.js"]
|
||||
CMD ["node", "dist/index.js"]
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# TaskFlow - Personal Task Management Application
|
||||
|
||||
A modern, mobile-first task management application built with React, TypeScript, and Express. TaskFlow helps you organize and track your tasks efficiently with multiple views including calendar scheduling, kanban boards, and project templates.
|
||||
|
||||
## Features
|
||||
|
||||
- 📱 **Mobile-First Design** - Fully responsive with touch-friendly interactions
|
||||
- 📅 **Calendar View** - Schedule tasks with drag-and-drop functionality
|
||||
- 📊 **Kanban Board** - Visualize workflow with traditional, weekly, and monthly views
|
||||
- ⏱️ **Time Tracking** - Built-in timer to track time spent on tasks
|
||||
- 🏷️ **Labels & Priorities** - Color-coded organization system
|
||||
- 📁 **Project Templates** - Reusable templates for common workflows
|
||||
- 🌓 **Dark Mode** - Automatic theme switching
|
||||
- 🐳 **Docker Ready** - Production-ready Docker deployment
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Frontend**: React 18, TypeScript, Tailwind CSS, shadcn/ui
|
||||
- **Backend**: Express.js, Node.js
|
||||
- **Database**: PostgreSQL with Drizzle ORM
|
||||
- **State Management**: TanStack Query
|
||||
- **Build Tools**: Vite, ESBuild
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Development (Local)
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Start development server:**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. **Open in browser:**
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
The development version uses in-memory storage by default.
|
||||
|
||||
### Production (Docker)
|
||||
|
||||
See [DEPLOYMENT.md](./DEPLOYMENT.md) for comprehensive Docker deployment instructions.
|
||||
|
||||
**Quick Docker Start:**
|
||||
|
||||
1. **Create `.env` file:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. **Start services:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
3. **Access application:**
|
||||
```
|
||||
http://localhost:5000
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
taskflow/
|
||||
├── client/ # React frontend
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # Reusable UI components
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ └── lib/ # Utilities and hooks
|
||||
│ └── index.html
|
||||
├── server/ # Express backend
|
||||
│ ├── index.ts # Server entry point
|
||||
│ ├── routes.ts # API routes
|
||||
│ ├── storage.ts # Storage implementations
|
||||
│ └── db.ts # Database connection
|
||||
├── shared/ # Shared types and schemas
|
||||
│ └── schema.ts # Database schema & types
|
||||
├── Dockerfile # Production Docker image
|
||||
├── docker-compose.yml # Docker orchestration
|
||||
└── DEPLOYMENT.md # Deployment guide
|
||||
```
|
||||
|
||||
## Available Scripts
|
||||
|
||||
- `npm run dev` - Start development server with hot reload
|
||||
- `npm run build` - Build for production
|
||||
- `npm start` - Start production server
|
||||
- `npm run check` - TypeScript type checking
|
||||
- `npm run db:push` - Push database schema changes
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Development
|
||||
```env
|
||||
NODE_ENV=development
|
||||
PORT=5000
|
||||
```
|
||||
|
||||
### Production (Docker)
|
||||
```env
|
||||
NODE_ENV=production
|
||||
DATABASE_URL=postgresql://user:password@postgres:5432/taskflow
|
||||
PORT=5000
|
||||
```
|
||||
|
||||
See `.env.example` for all available options.
|
||||
|
||||
## Database
|
||||
|
||||
TaskFlow uses PostgreSQL with Drizzle ORM for type-safe database operations.
|
||||
|
||||
### Schema
|
||||
|
||||
- **users** - User accounts (future multi-user support)
|
||||
- **labels** - Color-coded task labels
|
||||
- **tasks** - Task items with metadata
|
||||
|
||||
### Switching to Database in Development
|
||||
|
||||
To use the PostgreSQL database in development:
|
||||
|
||||
1. Set environment variable:
|
||||
```bash
|
||||
export USE_DB=true
|
||||
```
|
||||
|
||||
2. Ensure `DATABASE_URL` is set in your environment
|
||||
|
||||
3. Start the development server
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Labels
|
||||
- `GET /api/labels` - Get all labels
|
||||
- `GET /api/labels/:id` - Get specific label
|
||||
- `POST /api/labels` - Create new label
|
||||
- `PATCH /api/labels/:id` - Update label
|
||||
- `DELETE /api/labels/:id` - Delete label
|
||||
|
||||
### Tasks
|
||||
- `GET /api/tasks` - Get all tasks
|
||||
- `GET /api/tasks/:id` - Get specific task
|
||||
- `POST /api/tasks` - Create new task
|
||||
- `PATCH /api/tasks/:id` - Update task
|
||||
- `DELETE /api/tasks/:id` - Delete task
|
||||
|
||||
### Health
|
||||
- `GET /api/health` - Health check endpoint
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch
|
||||
3. Commit your changes
|
||||
4. Push to the branch
|
||||
5. Create a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details
|
||||
|
||||
## Support
|
||||
|
||||
For deployment help, see [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||
|
||||
For issues and questions, please open an issue on the repository.
|
||||
Generated
+115
-102
@@ -40,6 +40,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/pg": "^8.15.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@@ -57,6 +58,7 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.16.3",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -1389,6 +1391,74 @@
|
||||
"@types/pg": "8.11.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/@types/pg": {
|
||||
"version": "8.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz",
|
||||
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/pg-types": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz",
|
||||
"integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"pg-numeric": "1.0.2",
|
||||
"postgres-array": "~3.0.1",
|
||||
"postgres-bytea": "~3.0.0",
|
||||
"postgres-date": "~2.1.0",
|
||||
"postgres-interval": "^3.0.0",
|
||||
"postgres-range": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/postgres-array": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
|
||||
"integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/postgres-bytea": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
||||
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obuf": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/postgres-date": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
||||
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless/node_modules/postgres-interval": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
||||
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -3514,14 +3584,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz",
|
||||
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
||||
"version": "8.15.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz",
|
||||
"integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^4.0.1"
|
||||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@@ -6456,22 +6526,22 @@
|
||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||
},
|
||||
"node_modules/pg": {
|
||||
"version": "8.13.1",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
||||
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
||||
"version": "8.16.3",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.7.0",
|
||||
"pg-pool": "^3.7.0",
|
||||
"pg-protocol": "^1.7.0",
|
||||
"pg-types": "^2.1.0",
|
||||
"pgpass": "1.x"
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
"pg-protocol": "^1.10.3",
|
||||
"pg-types": "2.2.0",
|
||||
"pgpass": "1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
"node": ">= 16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pg-cloudflare": "^1.1.1"
|
||||
"pg-cloudflare": "^1.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-native": ">=3.0.1"
|
||||
@@ -6483,16 +6553,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pg-cloudflare": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/pg-connection-string": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
|
||||
"integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
@@ -6514,39 +6584,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pg-pool": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
||||
"integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
|
||||
"integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
||||
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz",
|
||||
"integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"pg-numeric": "1.0.2",
|
||||
"postgres-array": "~3.0.1",
|
||||
"postgres-bytea": "~3.0.0",
|
||||
"postgres-date": "~2.1.0",
|
||||
"postgres-interval": "^3.0.0",
|
||||
"postgres-range": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
@@ -6562,45 +6614,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg/node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pgpass": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
@@ -6790,42 +6803,42 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
|
||||
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
||||
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obuf": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
||||
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
||||
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-range": {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.0",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/pg": "^8.15.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@@ -59,6 +60,7 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.16.3",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -60,6 +60,27 @@ Preferred communication style: Simple, everyday language.
|
||||
|
||||
**Priority and Status Management**: Tasks support priority levels (low, medium, high) and status tracking (todo, in progress, done) with visual indicators.
|
||||
|
||||
## Production Deployment
|
||||
|
||||
**Docker Stack Support**: The application is production-ready with full Docker support for containerized deployment:
|
||||
- **Multi-stage Docker Build**: Optimized production image with separate build and runtime stages
|
||||
- **PostgreSQL Database**: Separate database container with persistent volume storage
|
||||
- **Health Monitoring**: Built-in health checks for both application and database services
|
||||
- **Environment Configuration**: Flexible configuration via environment variables
|
||||
- **Graceful Shutdown**: Proper cleanup and connection pool management
|
||||
- **Database Migrations**: Automatic schema creation and seeding on first startup
|
||||
|
||||
**Storage Flexibility**:
|
||||
- **Development**: Uses in-memory storage (MemStorage) for rapid prototyping
|
||||
- **Production**: Switches to PostgreSQL-backed storage (DbStorage) using Drizzle ORM
|
||||
- **Environment-driven**: Automatically selects storage based on NODE_ENV or USE_DB flag
|
||||
|
||||
**Database Management**:
|
||||
- **Connection Pooling**: Uses node-postgres (pg) for efficient connection management
|
||||
- **Auto-migrations**: Enables pgcrypto extension and creates tables on startup
|
||||
- **Default Data**: Seeds default labels automatically on fresh database
|
||||
- **Graceful Cleanup**: Properly closes connections on shutdown signals
|
||||
|
||||
## External Dependencies
|
||||
|
||||
### UI and Styling
|
||||
|
||||
+73
-4
@@ -1,8 +1,11 @@
|
||||
import { drizzle } from 'drizzle-orm/neon-http';
|
||||
import { neon } from '@neondatabase/serverless';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import * as schema from '@shared/schema';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
let db: ReturnType<typeof drizzle> | null = null;
|
||||
let pool: Pool | null = null;
|
||||
|
||||
export function getDatabase() {
|
||||
if (!db) {
|
||||
@@ -12,15 +15,72 @@ export function getDatabase() {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
|
||||
const sql = neon(databaseUrl);
|
||||
db = drizzle(sql, { schema });
|
||||
pool = new Pool({
|
||||
connectionString: databaseUrl,
|
||||
});
|
||||
|
||||
db = drizzle(pool, { schema });
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function runMigrations() {
|
||||
try {
|
||||
const database = getDatabase();
|
||||
|
||||
// Push schema to database (creates/updates tables as needed)
|
||||
// This is equivalent to running `drizzle-kit push`
|
||||
console.log('Checking database schema...');
|
||||
|
||||
// Enable pgcrypto extension for gen_random_uuid()
|
||||
await database.execute(sql`CREATE EXTENSION IF NOT EXISTS "pgcrypto"`);
|
||||
|
||||
// Create tables if they don't exist
|
||||
await database.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
await database.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS labels (
|
||||
id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
color TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
await database.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'todo',
|
||||
priority TEXT NOT NULL DEFAULT 'medium',
|
||||
due_date TIMESTAMP,
|
||||
time_tracked INTEGER NOT NULL DEFAULT 0,
|
||||
is_tracking BOOLEAN NOT NULL DEFAULT false,
|
||||
project_id TEXT,
|
||||
notes TEXT,
|
||||
label_id VARCHAR REFERENCES labels(id)
|
||||
)
|
||||
`);
|
||||
|
||||
console.log('✓ Database schema is up to date');
|
||||
} catch (error) {
|
||||
console.error('Failed to run migrations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeDatabase() {
|
||||
try {
|
||||
// Run migrations first
|
||||
await runMigrations();
|
||||
|
||||
const database = getDatabase();
|
||||
|
||||
// Create default labels if they don't exist
|
||||
@@ -44,3 +104,12 @@ export async function initializeDatabase() {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeDatabase() {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
pool = null;
|
||||
db = null;
|
||||
console.log('✓ Database connection closed');
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -1,7 +1,7 @@
|
||||
import express, { type Request, Response, NextFunction } from "express";
|
||||
import { registerRoutes } from "./routes";
|
||||
import { setupVite, serveStatic, log } from "./vite";
|
||||
import { initializeDatabase } from "./db";
|
||||
import { initializeDatabase, closeDatabase } from "./db";
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -79,4 +79,23 @@ app.use((req, res, next) => {
|
||||
}, () => {
|
||||
log(`serving on port ${port}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const gracefulShutdown = async (signal: string) => {
|
||||
log(`${signal} received, closing server gracefully...`);
|
||||
server.close(async () => {
|
||||
log('HTTP server closed');
|
||||
await closeDatabase();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Force close after 30 seconds
|
||||
setTimeout(() => {
|
||||
console.error('Forced shutdown after timeout');
|
||||
process.exit(1);
|
||||
}, 30000);
|
||||
};
|
||||
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user