Compare commits
3 Commits
4133052165
...
9ab9b41588
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ab9b41588 | |||
| 32e5fbe6ac | |||
| 784968521a |
@@ -18,10 +18,6 @@ externalPort = 80
|
|||||||
localPort = 35345
|
localPort = 35345
|
||||||
externalPort = 3002
|
externalPort = 3002
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 37553
|
|
||||||
externalPort = 4200
|
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 39063
|
localPort = 39063
|
||||||
externalPort = 3001
|
externalPort = 3001
|
||||||
|
|||||||
+1
-3
@@ -25,8 +25,6 @@ COPY --from=dependencies /tmp/prod_node_modules ./node_modules
|
|||||||
|
|
||||||
# Copy built application
|
# Copy built application
|
||||||
COPY --from=build /app/dist ./dist
|
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/drizzle.config.ts ./
|
||||||
COPY --from=build /app/package.json ./
|
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)})"
|
CMD node -e "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
|
||||||
|
|
||||||
# Start the application
|
# 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-toggle-group": "^1.1.3",
|
||||||
"@radix-ui/react-tooltip": "^1.2.0",
|
"@radix-ui/react-tooltip": "^1.2.0",
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
|
"@types/pg": "^8.15.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -1389,6 +1391,74 @@
|
|||||||
"@types/pg": "8.11.6"
|
"@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": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -3514,14 +3584,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg": {
|
"node_modules/@types/pg": {
|
||||||
"version": "8.11.6",
|
"version": "8.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz",
|
||||||
"integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==",
|
"integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"pg-protocol": "*",
|
"pg-protocol": "*",
|
||||||
"pg-types": "^4.0.1"
|
"pg-types": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
@@ -6456,22 +6526,22 @@
|
|||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
"node_modules/pg": {
|
"node_modules/pg": {
|
||||||
"version": "8.13.1",
|
"version": "8.16.3",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.7.0",
|
"pg-connection-string": "^2.9.1",
|
||||||
"pg-pool": "^3.7.0",
|
"pg-pool": "^3.10.1",
|
||||||
"pg-protocol": "^1.7.0",
|
"pg-protocol": "^1.10.3",
|
||||||
"pg-types": "^2.1.0",
|
"pg-types": "2.2.0",
|
||||||
"pgpass": "1.x"
|
"pgpass": "1.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.0.0"
|
"node": ">= 16.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"pg-cloudflare": "^1.1.1"
|
"pg-cloudflare": "^1.2.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"pg-native": ">=3.0.1"
|
"pg-native": ">=3.0.1"
|
||||||
@@ -6483,16 +6553,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-cloudflare": {
|
"node_modules/pg-cloudflare": {
|
||||||
"version": "1.1.1",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||||
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/pg-connection-string": {
|
"node_modules/pg-connection-string": {
|
||||||
"version": "2.7.0",
|
"version": "2.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||||
"integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
|
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-int8": {
|
"node_modules/pg-int8": {
|
||||||
@@ -6514,39 +6584,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-pool": {
|
"node_modules/pg-pool": {
|
||||||
"version": "3.7.0",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||||
"integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
|
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"pg": ">=8.0"
|
"pg": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-protocol": {
|
"node_modules/pg-protocol": {
|
||||||
"version": "1.7.0",
|
"version": "1.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
||||||
"integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
|
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pg-types": {
|
"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",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
@@ -6562,45 +6614,6 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/pgpass": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
@@ -6790,42 +6803,42 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/postgres-array": {
|
"node_modules/postgres-array": {
|
||||||
"version": "3.0.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
"integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==",
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres-bytea": {
|
"node_modules/postgres-bytea": {
|
||||||
"version": "3.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
"integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"obuf": "~1.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres-date": {
|
"node_modules/postgres-date": {
|
||||||
"version": "2.1.0",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
"integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres-interval": {
|
"node_modules/postgres-interval": {
|
||||||
"version": "3.0.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
"integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postgres-range": {
|
"node_modules/postgres-range": {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.3",
|
"@radix-ui/react-toggle-group": "^1.1.3",
|
||||||
"@radix-ui/react-tooltip": "^1.2.0",
|
"@radix-ui/react-tooltip": "^1.2.0",
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
|
"@types/pg": "^8.15.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.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.
|
**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
|
## External Dependencies
|
||||||
|
|
||||||
### UI and Styling
|
### UI and Styling
|
||||||
|
|||||||
+73
-4
@@ -1,8 +1,11 @@
|
|||||||
import { drizzle } from 'drizzle-orm/neon-http';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import { neon } from '@neondatabase/serverless';
|
import { Pool } from 'pg';
|
||||||
|
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||||
import * as schema from '@shared/schema';
|
import * as schema from '@shared/schema';
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
let db: ReturnType<typeof drizzle> | null = null;
|
let db: ReturnType<typeof drizzle> | null = null;
|
||||||
|
let pool: Pool | null = null;
|
||||||
|
|
||||||
export function getDatabase() {
|
export function getDatabase() {
|
||||||
if (!db) {
|
if (!db) {
|
||||||
@@ -12,15 +15,72 @@ export function getDatabase() {
|
|||||||
throw new Error('DATABASE_URL environment variable is not set');
|
throw new Error('DATABASE_URL environment variable is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sql = neon(databaseUrl);
|
pool = new Pool({
|
||||||
db = drizzle(sql, { schema });
|
connectionString: databaseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
db = drizzle(pool, { schema });
|
||||||
}
|
}
|
||||||
|
|
||||||
return db;
|
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() {
|
export async function initializeDatabase() {
|
||||||
try {
|
try {
|
||||||
|
// Run migrations first
|
||||||
|
await runMigrations();
|
||||||
|
|
||||||
const database = getDatabase();
|
const database = getDatabase();
|
||||||
|
|
||||||
// Create default labels if they don't exist
|
// Create default labels if they don't exist
|
||||||
@@ -44,3 +104,12 @@ export async function initializeDatabase() {
|
|||||||
throw error;
|
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 express, { type Request, Response, NextFunction } from "express";
|
||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
import { setupVite, serveStatic, log } from "./vite";
|
import { setupVite, serveStatic, log } from "./vite";
|
||||||
import { initializeDatabase } from "./db";
|
import { initializeDatabase, closeDatabase } from "./db";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -79,4 +79,23 @@ app.use((req, res, next) => {
|
|||||||
}, () => {
|
}, () => {
|
||||||
log(`serving on port ${port}`);
|
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