Compare commits

...

3 Commits

Author SHA1 Message Date
paul-nothaft 9ab9b41588 Prepare application for production deployment with Docker and PostgreSQL
No changes to be committed.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/ahHWEFX
2025-10-23 13:32:35 +00:00
paul-nothaft 32e5fbe6ac Prepare application for production deployment with Docker and PostgreSQL support
Update README and replit.md to include comprehensive Docker deployment instructions, environment variable configurations, and database management details.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/ahHWEFX
2025-10-23 13:32:11 +00:00
paul-nothaft 784968521a Prepare application for production deployment with Docker and PostgreSQL
Integrate PostgreSQL database with Drizzle ORM, implement database migrations, and enhance Dockerfile for production build.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/ahHWEFX
2025-10-23 13:31:02 +00:00
8 changed files with 403 additions and 114 deletions
-4
View File
@@ -18,10 +18,6 @@ externalPort = 80
localPort = 35345
externalPort = 3002
[[ports]]
localPort = 37553
externalPort = 4200
[[ports]]
localPort = 39063
externalPort = 3001
+1 -3
View File
@@ -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"]
+171
View File
@@ -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.
+115 -102
View File
@@ -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": {
+2
View File
@@ -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",
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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'));
})();