Compare commits
2 Commits
c70351ebeb
...
38ee5a8886
| Author | SHA1 | Date | |
|---|---|---|---|
| 38ee5a8886 | |||
| 2d31c49235 |
+30
-11
@@ -1,14 +1,33 @@
|
|||||||
# Database
|
# Environment Configuration Template
|
||||||
DATABASE_URL="./sqlite.db"
|
# Copy this to .env.production and fill in your values
|
||||||
|
|
||||||
# NextAuth.js
|
# === REQUIRED VARIABLES ===
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
# Database URL (host path - gets mounted into container)
|
||||||
NEXTAUTH_SECRET="your-secret-key-here-make-this-very-long-and-random"
|
DATABASE_URL=./data/sqlite.db
|
||||||
|
|
||||||
# Email Configuration (Gmail)
|
# NextAuth.js Configuration (REQUIRED)
|
||||||
EMAIL_USER="your-email@gmail.com"
|
NEXTAUTH_URL=https://your-domain.com
|
||||||
EMAIL_PASSWORD="your-app-password-here"
|
NEXTAUTH_SECRET=your-long-random-secret-here-generate-with-openssl-rand-base64-32
|
||||||
|
|
||||||
# Admin Configuration
|
# Admin User (CHANGE THESE!)
|
||||||
ADMIN_EMAIL="admin@example.com"
|
ADMIN_EMAIL=admin@your-domain.com
|
||||||
ADMIN_PASSWORD="admin123"
|
ADMIN_PASSWORD=your-secure-admin-password
|
||||||
|
|
||||||
|
# === OPTIONAL VARIABLES ===
|
||||||
|
# Application Environment
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Email Configuration (for notifications - optional)
|
||||||
|
EMAIL_USER=your-email@gmail.com
|
||||||
|
EMAIL_PASSWORD=your-gmail-app-password
|
||||||
|
|
||||||
|
# Rate Limiting (defaults provided)
|
||||||
|
RATE_LIMIT_MAX=100
|
||||||
|
RATE_LIMIT_WINDOW=900000
|
||||||
|
|
||||||
|
# Logging Level
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Local Development Override (for HTTP testing)
|
||||||
|
DISABLE_SECURE_COOKIES=false
|
||||||
|
|||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
# Use the official Node.js runtime as the base image
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package.json and package-lock.json (if available)
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci --only=production
|
|
||||||
|
|
||||||
# Copy the rest of the application code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Create the SQLite database directory
|
|
||||||
RUN mkdir -p /app/data
|
|
||||||
|
|
||||||
# Build the Next.js application
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Expose the port the app runs on
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
# Set environment variables
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV DATABASE_URL=/app/data/sqlite.db
|
|
||||||
|
|
||||||
# Create a non-root user to run the application
|
|
||||||
RUN addgroup -g 1001 -S nodejs
|
|
||||||
RUN adduser -S nextjs -u 1001
|
|
||||||
|
|
||||||
# Change ownership of the app directory to the nextjs user
|
|
||||||
RUN chown -R nextjs:nodejs /app
|
|
||||||
USER nextjs
|
|
||||||
|
|
||||||
# Command to run the application
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
+10
-3
@@ -54,14 +54,21 @@ COPY --chown=nextjs:nodejs <<EOF /app/start.sh
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
echo "🚀 Starting TT Booking Production..."
|
echo "🚀 Starting TT Booking Production..."
|
||||||
|
|
||||||
|
# Ensure database directory has proper permissions
|
||||||
|
if [ -d "/app/data" ]; then
|
||||||
|
echo "📁 Setting database permissions..."
|
||||||
|
chmod -R 755 /app/data /app/backups /app/logs 2>/dev/null || true
|
||||||
|
if [ -f "/app/data/sqlite.db" ]; then
|
||||||
|
chmod 644 /app/data/sqlite.db 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo "🌟 Starting server..."
|
echo "🌟 Starting server..."
|
||||||
exec node server.js
|
exec node server.js
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
RUN chmod +x /app/start.sh
|
RUN chmod +x /app/start.sh
|
||||||
|
|
||||||
USER nextjs
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
# LCC Table Tennis Booking - Production Setup
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
Your production environment is configured for domain: **lcc-tt-booking.mikicvi.com**
|
|
||||||
|
|
||||||
### 1. Deploy the Application
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Make deployment script executable (if not already done)
|
|
||||||
chmod +x deploy.sh
|
|
||||||
|
|
||||||
# Deploy to production
|
|
||||||
./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Set Up Cloudflare Tunnel
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run the tunnel setup script
|
|
||||||
./setup-tunnel.sh
|
|
||||||
|
|
||||||
# Follow the instructions provided by the script
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Files Created
|
|
||||||
|
|
||||||
### Environment Configuration
|
|
||||||
|
|
||||||
- **`.env.production`** - Production environment variables
|
|
||||||
- **`docker-compose.production.yml`** - Production Docker Compose configuration
|
|
||||||
- **`Dockerfile.production`** - Optimized production Docker build
|
|
||||||
|
|
||||||
### Security & Secrets
|
|
||||||
|
|
||||||
- **NEXTAUTH_SECRET**: `qHYNaq516ByAY6H4HdxacMICd05I1DqvrTitIuVtT20=` (pre-generated)
|
|
||||||
- **Domain**: `lcc-tt-booking.mikicvi.com`
|
|
||||||
- **Admin Email**: `admin@lcc-tt-booking.mikicvi.com`
|
|
||||||
|
|
||||||
### Scripts & Automation
|
|
||||||
|
|
||||||
- **`deploy.sh`** - One-command production deployment
|
|
||||||
- **`setup-tunnel.sh`** - Cloudflare Tunnel setup assistant
|
|
||||||
- **`cloudflare-tunnel-config.yml`** - Tunnel configuration template
|
|
||||||
|
|
||||||
### Health & Monitoring
|
|
||||||
|
|
||||||
- **`/api/health`** - Health check endpoint with database connectivity and memory usage
|
|
||||||
- **Automated backups** - Daily SQLite backups (30-day retention)
|
|
||||||
- **Log rotation** - Automatic log management
|
|
||||||
|
|
||||||
## Production Checklist
|
|
||||||
|
|
||||||
### Before Going Live:
|
|
||||||
|
|
||||||
- [ ] Update email credentials in `.env.production`
|
|
||||||
- [ ] Change admin password from default
|
|
||||||
- [ ] Set up Cloudflare Tunnel
|
|
||||||
- [ ] Test health check endpoint
|
|
||||||
- [ ] Verify SSL certificate is working
|
|
||||||
|
|
||||||
### After Deployment:
|
|
||||||
|
|
||||||
- [ ] Test all booking functionality
|
|
||||||
- [ ] Verify admin panel access
|
|
||||||
- [ ] Check automated backups are working
|
|
||||||
- [ ] Set up monitoring alerts (optional)
|
|
||||||
|
|
||||||
## Management Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View application logs
|
|
||||||
docker-compose -f docker-compose.production.yml logs -f tt-booking
|
|
||||||
|
|
||||||
# Restart application
|
|
||||||
docker-compose -f docker-compose.production.yml restart tt-booking
|
|
||||||
|
|
||||||
# Stop all services
|
|
||||||
docker-compose -f docker-compose.production.yml down
|
|
||||||
|
|
||||||
# Update and redeploy
|
|
||||||
./deploy.sh
|
|
||||||
|
|
||||||
# Access database backup
|
|
||||||
ls -la backups/
|
|
||||||
|
|
||||||
# Check application health
|
|
||||||
curl http://localhost:3000/api/health
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
/Users/mikicv/Documents/tt-booking/
|
|
||||||
├── .env.production # Production environment variables
|
|
||||||
├── docker-compose.production.yml # Production Docker Compose
|
|
||||||
├── Dockerfile.production # Production Docker build
|
|
||||||
├── deploy.sh # Deployment script
|
|
||||||
├── setup-tunnel.sh # Cloudflare Tunnel setup
|
|
||||||
├── cloudflare-tunnel-config.yml # Tunnel configuration
|
|
||||||
├── data/ # SQLite database storage
|
|
||||||
├── backups/ # Automated backups
|
|
||||||
└── logs/ # Application logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Default Admin Access
|
|
||||||
|
|
||||||
- **URL**: https://lcc-tt-booking.mikicvi.com/admin
|
|
||||||
- **Email**: admin@lcc-tt-booking.mikicvi.com
|
|
||||||
- **Password**: ChangeMeInProduction123! (⚠️ CHANGE THIS!)
|
|
||||||
|
|
||||||
## Support & Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
|
|
||||||
1. **Container won't start**: Check `docker-compose -f docker-compose.production.yml logs`
|
|
||||||
2. **Database issues**: Ensure `data/` directory permissions are correct
|
|
||||||
3. **Tunnel not working**: Verify Cloudflare DNS settings and tunnel configuration
|
|
||||||
|
|
||||||
### Health Check:
|
|
||||||
|
|
||||||
The health endpoint (`/api/health`) provides:
|
|
||||||
|
|
||||||
- Application status
|
|
||||||
- Database connectivity
|
|
||||||
- Memory usage
|
|
||||||
- Uptime information
|
|
||||||
|
|
||||||
### Backup Verification:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List all backups
|
|
||||||
ls -la backups/
|
|
||||||
|
|
||||||
# Check latest backup size
|
|
||||||
du -h backups/sqlite-$(date +%Y%m%d)*.db | tail -1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production Features Included:
|
|
||||||
|
|
||||||
- ✅ Automated daily backups (30-day retention)
|
|
||||||
- ✅ Health monitoring endpoint
|
|
||||||
- ✅ Log rotation and management
|
|
||||||
- ✅ Multi-stage Docker optimization
|
|
||||||
- ✅ Security hardening
|
|
||||||
- ✅ Rate limiting configured
|
|
||||||
- ✅ SSL-ready with Cloudflare integration
|
|
||||||
|
|
||||||
Your LCC Table Tennis Booking System is ready for production! 🏓
|
|
||||||
@@ -32,14 +32,21 @@ fi
|
|||||||
echo "🛠️ Setting up the database..."
|
echo "🛠️ Setting up the database..."
|
||||||
npx tsx scripts/setup-database.ts
|
npx tsx scripts/setup-database.ts
|
||||||
|
|
||||||
|
# Fix database permissions for container
|
||||||
|
echo "🔒 Fixing database permissions for container..."
|
||||||
|
if [ -f "data/sqlite.db" ]; then
|
||||||
|
chmod 666 data/sqlite.db
|
||||||
|
fi
|
||||||
|
chmod 777 data backups logs
|
||||||
|
|
||||||
# Build and deploy with Docker Compose
|
# Build and deploy with Docker Compose
|
||||||
echo "🐳 Building and starting Docker containers..."
|
echo "🐳 Building and starting Docker containers..."
|
||||||
|
|
||||||
# Stop existing containers
|
# Stop existing containers
|
||||||
docker-compose -f docker-compose.production.yml down || echo "No existing containers to stop"
|
docker compose -f docker-compose.production.yml down || echo "No existing containers to stop"
|
||||||
|
|
||||||
# Build and start containers
|
# Build and start containers
|
||||||
docker-compose -f docker-compose.production.yml up -d --build
|
docker compose -f docker-compose.production.yml up -d --build
|
||||||
|
|
||||||
# Wait for containers to be healthy
|
# Wait for containers to be healthy
|
||||||
echo "⏳ Waiting for containers to be healthy..."
|
echo "⏳ Waiting for containers to be healthy..."
|
||||||
@@ -48,13 +55,13 @@ sleep 30
|
|||||||
# Check health
|
# Check health
|
||||||
echo "🔍 Checking application health..."
|
echo "🔍 Checking application health..."
|
||||||
for i in {1..10}; do
|
for i in {1..10}; do
|
||||||
if curl -f http://localhost:3000/api/health >/dev/null 2>&1; then
|
if curl -f http://localhost:3036/api/health >/dev/null 2>&1; then
|
||||||
echo "✅ Application is healthy!"
|
echo "✅ Application is healthy!"
|
||||||
break
|
break
|
||||||
elif [ $i -eq 10 ]; then
|
elif [ $i -eq 10 ]; then
|
||||||
echo "❌ Application health check failed after 10 attempts"
|
echo "❌ Application health check failed after 10 attempts"
|
||||||
echo "📋 Container logs:"
|
echo "📋 Container logs:"
|
||||||
docker-compose -f docker-compose.production.yml logs tt-booking
|
docker compose -f docker-compose.production.yml logs tt-booking
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "⏳ Attempt $i/10: Application not ready yet, waiting..."
|
echo "⏳ Attempt $i/10: Application not ready yet, waiting..."
|
||||||
@@ -64,24 +71,24 @@ done
|
|||||||
|
|
||||||
# Show running containers
|
# Show running containers
|
||||||
echo "📊 Running containers:"
|
echo "📊 Running containers:"
|
||||||
docker-compose -f docker-compose.production.yml ps
|
docker compose -f docker-compose.production.yml ps
|
||||||
|
|
||||||
# Show logs
|
# Show logs
|
||||||
echo "📋 Recent application logs:"
|
echo "📋 Recent application logs:"
|
||||||
docker-compose -f docker-compose.production.yml logs --tail=20 tt-booking
|
docker compose -f docker-compose.production.yml logs --tail=20 tt-booking
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🎉 Deployment completed successfully!"
|
echo "🎉 Deployment completed successfully!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "📊 Application Status:"
|
echo "📊 Application Status:"
|
||||||
echo " • URL: https://lcc-tt-booking.mikicvi.com"
|
echo " • URL: https://lcc-tt-booking.mikicvi.com"
|
||||||
echo " • Health Check: http://localhost:3000/api/health"
|
echo " • Health Check: http://localhost:3036/api/health"
|
||||||
echo " • Container Status: $(docker-compose -f docker-compose.production.yml ps -q tt-booking | xargs docker inspect -f '{{.State.Status}}')"
|
echo " • Container Status: $(docker compose -f docker-compose.production.yml ps -q tt-booking | xargs docker inspect -f '{{.State.Status}}')"
|
||||||
echo ""
|
echo ""
|
||||||
echo "🔧 Useful commands:"
|
echo "🔧 Useful commands:"
|
||||||
echo " • View logs: docker-compose -f docker-compose.production.yml logs -f tt-booking"
|
echo " • View logs: docker compose -f docker-compose.production.yml logs -f tt-booking"
|
||||||
echo " • Restart: docker-compose -f docker-compose.production.yml restart tt-booking"
|
echo " • Restart: docker compose -f docker-compose.production.yml restart tt-booking"
|
||||||
echo " • Stop: docker-compose -f docker-compose.production.yml down"
|
echo " • Stop: docker compose -f docker-compose.production.yml down"
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ Don't forget to:"
|
echo "⚠️ Don't forget to:"
|
||||||
echo " 1. Set up Cloudflare Tunnel to expose your application"
|
echo " 1. Set up Cloudflare Tunnel to expose your application"
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
tt-booking:
|
tt-booking:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.production
|
||||||
container_name: lcc-tt-booking
|
container_name: lcc-tt-booking
|
||||||
|
user: "1000:1000"
|
||||||
ports:
|
ports:
|
||||||
- '3036:3000'
|
- '3036:3000'
|
||||||
|
env_file:
|
||||||
|
- .env.production
|
||||||
|
- '3036:3000'
|
||||||
env_file:
|
env_file:
|
||||||
- .env.production
|
- .env.production
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
# Container-specific override
|
||||||
- DATABASE_URL=/app/data/sqlite.db
|
|
||||||
- NEXTAUTH_URL=https://lcc-tt-booking.mikicvi.com
|
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
tt-booking:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- '3000:3000'
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- DATABASE_URL=/app/data/sqlite.db
|
|
||||||
- NEXTAUTH_URL=http://localhost:3000
|
|
||||||
- NEXTAUTH_SECRET=your-secret-key-here-make-this-very-long-and-random
|
|
||||||
- EMAIL_USER=your-email@gmail.com
|
|
||||||
- EMAIL_PASSWORD=your-app-password-here
|
|
||||||
- ADMIN_EMAIL=admin@example.com
|
|
||||||
- ADMIN_PASSWORD=admin123
|
|
||||||
volumes:
|
|
||||||
- ./data:/app/data
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
# Nginx reverse proxy (optional, for production deployment)
|
|
||||||
nginx:
|
|
||||||
image: nginx:alpine
|
|
||||||
ports:
|
|
||||||
- '80:80'
|
|
||||||
- '443:443'
|
|
||||||
volumes:
|
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
|
||||||
- ./ssl:/etc/nginx/ssl:ro
|
|
||||||
depends_on:
|
|
||||||
- tt-booking
|
|
||||||
restart: unless-stopped
|
|
||||||
+2
-2
@@ -49,7 +49,7 @@ export async function createSession(payload: Omit<SessionPayload, 'expiresAt'>)
|
|||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
cookieStore.set('session', session, {
|
cookieStore.set('session', session, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: process.env.NODE_ENV === 'production' && process.env.NEXTAUTH_URL?.startsWith('https'),
|
||||||
expires: expiresAt,
|
expires: expiresAt,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -70,7 +70,7 @@ export async function updateSession() {
|
|||||||
|
|
||||||
cookieStore.set('session', newSession, {
|
cookieStore.set('session', newSession, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: process.env.NODE_ENV === 'production' && process.env.NEXTAUTH_URL?.startsWith('https'),
|
||||||
expires: expires,
|
expires: expires,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|||||||
-78
@@ -1,78 +0,0 @@
|
|||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
upstream app {
|
|
||||||
server tt-booking:3000;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Rate limiting
|
|
||||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
|
||||||
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name your-domain.com;
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name your-domain.com;
|
|
||||||
|
|
||||||
# SSL configuration
|
|
||||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
|
||||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
|
|
||||||
# API rate limiting
|
|
||||||
location /api/ {
|
|
||||||
limit_req zone=api burst=20 nodelay;
|
|
||||||
proxy_pass http://app;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Login rate limiting
|
|
||||||
location /api/auth/login {
|
|
||||||
limit_req zone=login burst=3 nodelay;
|
|
||||||
proxy_pass http://app;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Static files and general requests
|
|
||||||
location / {
|
|
||||||
proxy_pass http://app;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Cleanup old seed scripts and organize database utilities
|
|
||||||
# This script consolidates old individual seed scripts into the new unified system
|
|
||||||
|
|
||||||
echo "🧹 Cleaning up old database scripts..."
|
|
||||||
|
|
||||||
# Create a backup directory for old scripts
|
|
||||||
mkdir -p scripts/old-seeds
|
|
||||||
|
|
||||||
# Move old individual seed scripts to backup
|
|
||||||
echo "📦 Moving old seed scripts to scripts/old-seeds/..."
|
|
||||||
|
|
||||||
if [ -f "scripts/seed-data.ts" ]; then
|
|
||||||
mv scripts/seed-data.ts scripts/old-seeds/
|
|
||||||
echo " ✓ Moved seed-data.ts"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "scripts/seed-announcements.ts" ]; then
|
|
||||||
mv scripts/seed-announcements.ts scripts/old-seeds/
|
|
||||||
echo " ✓ Moved seed-announcements.ts"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "scripts/seed-time-slots.ts" ]; then
|
|
||||||
mv scripts/seed-time-slots.ts scripts/old-seeds/
|
|
||||||
echo " ✓ Moved seed-time-slots.ts"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "scripts/init-admin-data.ts" ]; then
|
|
||||||
mv scripts/init-admin-data.ts scripts/old-seeds/
|
|
||||||
echo " ✓ Moved init-admin-data.ts"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove old reset script if it exists
|
|
||||||
if [ -f "scripts/reset-database.ts" ]; then
|
|
||||||
mv scripts/reset-database.ts scripts/old-seeds/
|
|
||||||
echo " ✓ Moved old reset-database.ts"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create a README in the old-seeds directory
|
|
||||||
cat > scripts/old-seeds/README.md << 'EOF'
|
|
||||||
# Old Seed Scripts (Archived)
|
|
||||||
|
|
||||||
These are the original individual seed scripts that have been consolidated into the new unified database setup system.
|
|
||||||
|
|
||||||
## Consolidated Into:
|
|
||||||
|
|
||||||
- **setup-database.ts** - Unified setup script with all functionality
|
|
||||||
- **reset-db.ts** - Improved reset script with safety features
|
|
||||||
|
|
||||||
## Original Scripts:
|
|
||||||
|
|
||||||
- `seed-data.ts` - Sample bookings and activity logs
|
|
||||||
- `seed-announcements.ts` - Test announcements
|
|
||||||
- `seed-time-slots.ts` - Time slot configuration
|
|
||||||
- `init-admin-data.ts` - Admin dashboard initialization
|
|
||||||
- `reset-database.ts` - Original reset script
|
|
||||||
|
|
||||||
## Migration Notes:
|
|
||||||
|
|
||||||
All functionality from these scripts has been intelligently integrated into the new system:
|
|
||||||
|
|
||||||
### New Command Equivalents:
|
|
||||||
|
|
||||||
| Old Script | New Command |
|
|
||||||
|------------|-------------|
|
|
||||||
| `tsx scripts/seed-data.ts` | `npm run db:setup` |
|
|
||||||
| `tsx scripts/seed-announcements.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/seed-time-slots.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/init-admin-data.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/reset-database.ts` | `npm run db:reset-confirm` |
|
|
||||||
|
|
||||||
### Advantages of New System:
|
|
||||||
|
|
||||||
1. **Intelligent Setup** - Automatically handles dependencies and order
|
|
||||||
2. **Flexible Options** - Essential-only or full sample data modes
|
|
||||||
3. **Safety Features** - Reset confirmation and detailed logging
|
|
||||||
4. **Better Documentation** - Comprehensive help and summaries
|
|
||||||
5. **Single Source** - All database setup in one place
|
|
||||||
|
|
||||||
These old scripts are kept for reference but should not be used in new development.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo " ✓ Created README in old-seeds directory"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ Cleanup complete!"
|
|
||||||
echo ""
|
|
||||||
echo "📋 Summary of changes:"
|
|
||||||
echo " • Old seed scripts moved to scripts/old-seeds/"
|
|
||||||
echo " • New unified system active:"
|
|
||||||
echo " - setup-database.ts (comprehensive setup)"
|
|
||||||
echo " - reset-db.ts (safe reset with confirmation)"
|
|
||||||
echo ""
|
|
||||||
echo "🚀 New database commands:"
|
|
||||||
echo " npm run db:setup # Full setup with sample data"
|
|
||||||
echo " npm run db:seed # Essential data only"
|
|
||||||
echo " npm run db:reset # Safe reset (shows warning)"
|
|
||||||
echo " npm run db:reset-confirm # Immediate reset"
|
|
||||||
echo ""
|
|
||||||
echo "📖 See DATABASE_SETUP.md for detailed documentation"
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Old Seed Scripts (Archived)
|
|
||||||
|
|
||||||
These are the original individual seed scripts that have been consolidated into the new unified database setup system.
|
|
||||||
|
|
||||||
## Consolidated Into:
|
|
||||||
|
|
||||||
- **setup-database.ts** - Unified setup script with all functionality
|
|
||||||
- **reset-db.ts** - Improved reset script with safety features
|
|
||||||
|
|
||||||
## Original Scripts:
|
|
||||||
|
|
||||||
- `seed-data.ts` - Sample bookings and activity logs
|
|
||||||
- `seed-announcements.ts` - Test announcements
|
|
||||||
- `seed-time-slots.ts` - Time slot configuration
|
|
||||||
- `init-admin-data.ts` - Admin dashboard initialization
|
|
||||||
- `reset-database.ts` - Original reset script
|
|
||||||
|
|
||||||
## Migration Notes:
|
|
||||||
|
|
||||||
All functionality from these scripts has been intelligently integrated into the new system:
|
|
||||||
|
|
||||||
### New Command Equivalents:
|
|
||||||
|
|
||||||
| Old Script | New Command |
|
|
||||||
|------------|-------------|
|
|
||||||
| `tsx scripts/seed-data.ts` | `npm run db:setup` |
|
|
||||||
| `tsx scripts/seed-announcements.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/seed-time-slots.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/init-admin-data.ts` | Integrated into setup |
|
|
||||||
| `tsx scripts/reset-database.ts` | `npm run db:reset-confirm` |
|
|
||||||
|
|
||||||
### Advantages of New System:
|
|
||||||
|
|
||||||
1. **Intelligent Setup** - Automatically handles dependencies and order
|
|
||||||
2. **Flexible Options** - Essential-only or full sample data modes
|
|
||||||
3. **Safety Features** - Reset confirmation and detailed logging
|
|
||||||
4. **Better Documentation** - Comprehensive help and summaries
|
|
||||||
5. **Single Source** - All database setup in one place
|
|
||||||
|
|
||||||
These old scripts are kept for reference but should not be used in new development.
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { db } from '../lib/db';
|
|
||||||
import { settings, metrics } from '../lib/db/schema';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import { eq } from 'drizzle-orm';
|
|
||||||
|
|
||||||
async function initializeAdminData() {
|
|
||||||
try {
|
|
||||||
console.log('Initializing admin dashboard data...');
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// Initialize default settings
|
|
||||||
console.log('Setting up default settings...');
|
|
||||||
|
|
||||||
const defaultSettings = [
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'booking_window_days',
|
|
||||||
value: '14',
|
|
||||||
updatedAt: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'max_booking_duration_hours',
|
|
||||||
value: '2',
|
|
||||||
updatedAt: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'max_bookings_per_user_per_hour_per_day',
|
|
||||||
value: '1',
|
|
||||||
updatedAt: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'allow_booking_modifications',
|
|
||||||
value: 'true',
|
|
||||||
updatedAt: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'booking_modification_hours_before',
|
|
||||||
value: '2',
|
|
||||||
updatedAt: now,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Insert settings if they don't exist
|
|
||||||
for (const setting of defaultSettings) {
|
|
||||||
const existingSetting = await db.select().from(settings).where(eq(settings.key, setting.key)).limit(1);
|
|
||||||
|
|
||||||
if (existingSetting.length === 0) {
|
|
||||||
await db.insert(settings).values(setting);
|
|
||||||
console.log(`✓ Setting '${setting.key}' initialized with value '${setting.value}'`);
|
|
||||||
} else {
|
|
||||||
console.log(`- Setting '${setting.key}' already exists`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize current month's metrics
|
|
||||||
console.log('Initializing monthly metrics...');
|
|
||||||
|
|
||||||
const currentMonth = now.toISOString().substring(0, 7); // "2025-09"
|
|
||||||
|
|
||||||
const existingMetric = await db.select().from(metrics).where(eq(metrics.period, currentMonth)).limit(1);
|
|
||||||
|
|
||||||
if (existingMetric.length === 0) {
|
|
||||||
const monthlyMetric = {
|
|
||||||
id: randomUUID(),
|
|
||||||
metricType: 'monthly_bookings',
|
|
||||||
period: currentMonth,
|
|
||||||
value: 0,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
};
|
|
||||||
|
|
||||||
await db.insert(metrics).values(monthlyMetric);
|
|
||||||
console.log(`✓ Monthly metrics initialized for ${currentMonth}`);
|
|
||||||
} else {
|
|
||||||
console.log(`- Monthly metrics for ${currentMonth} already exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Admin dashboard data initialization complete!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initializing admin data:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeAdminData();
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
import Database from 'better-sqlite3';
|
|
||||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|
||||||
import * as schema from '../lib/db/schema';
|
|
||||||
import { sql } from 'drizzle-orm';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
|
|
||||||
const sqlite = new Database('./sqlite.db');
|
|
||||||
const db = drizzle(sqlite, { schema });
|
|
||||||
|
|
||||||
async function resetDatabase() {
|
|
||||||
console.log('Resetting database...');
|
|
||||||
|
|
||||||
// Drop all tables
|
|
||||||
const tables = [
|
|
||||||
'activity_logs',
|
|
||||||
'bookings',
|
|
||||||
'announcements',
|
|
||||||
'time_slots',
|
|
||||||
'settings',
|
|
||||||
'courts',
|
|
||||||
'users',
|
|
||||||
'__drizzle_migrations',
|
|
||||||
'__old_push_courts',
|
|
||||||
'__old_push_users',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const table of tables) {
|
|
||||||
try {
|
|
||||||
await db.run(sql.raw(`DROP TABLE IF EXISTS ${table}`));
|
|
||||||
console.log(`Dropped table: ${table}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Table ${table} doesn't exist or error dropping:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create all tables with current schema
|
|
||||||
console.log('Creating tables...');
|
|
||||||
|
|
||||||
// Users table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
email TEXT NOT NULL UNIQUE,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
surname TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Courts table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE courts (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Settings table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE settings (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
key TEXT NOT NULL UNIQUE,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Time slots table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE time_slots (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
day_of_week INTEGER NOT NULL,
|
|
||||||
start_time TEXT NOT NULL,
|
|
||||||
end_time TEXT NOT NULL,
|
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Bookings table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE bookings (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
court_id TEXT NOT NULL REFERENCES courts(id) ON DELETE CASCADE,
|
|
||||||
date TEXT NOT NULL,
|
|
||||||
start_time TEXT NOT NULL,
|
|
||||||
end_time TEXT NOT NULL,
|
|
||||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cancelled')),
|
|
||||||
notes TEXT,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Announcements table with all required columns
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE announcements (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
title TEXT NOT NULL,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')),
|
|
||||||
expires_at INTEGER,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Activity logs table
|
|
||||||
await db.run(sql`
|
|
||||||
CREATE TABLE activity_logs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
||||||
action TEXT NOT NULL,
|
|
||||||
entity_type TEXT NOT NULL,
|
|
||||||
entity_id TEXT,
|
|
||||||
details TEXT,
|
|
||||||
ip_address TEXT,
|
|
||||||
user_agent TEXT,
|
|
||||||
created_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log('All tables created successfully!');
|
|
||||||
|
|
||||||
// Insert seed data
|
|
||||||
console.log('Inserting seed data...');
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
// Create admin user
|
|
||||||
const hashedPassword = await bcrypt.hash('admin123', 12);
|
|
||||||
await db.insert(schema.users).values({
|
|
||||||
id: randomUUID(),
|
|
||||||
email: 'admin@ttbooking.com',
|
|
||||||
name: 'Admin',
|
|
||||||
surname: 'User',
|
|
||||||
password: hashedPassword,
|
|
||||||
role: 'admin',
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create test user
|
|
||||||
const testPassword = await bcrypt.hash('password123', 12);
|
|
||||||
await db.insert(schema.users).values({
|
|
||||||
id: randomUUID(),
|
|
||||||
email: 'user@test.com',
|
|
||||||
name: 'Test',
|
|
||||||
surname: 'User',
|
|
||||||
password: testPassword,
|
|
||||||
role: 'user',
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create courts
|
|
||||||
const court1Id = randomUUID();
|
|
||||||
const court2Id = randomUUID();
|
|
||||||
|
|
||||||
await db.insert(schema.courts).values([
|
|
||||||
{
|
|
||||||
id: court1Id,
|
|
||||||
name: 'Court 1',
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: court2Id,
|
|
||||||
name: 'Court 2',
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Insert default settings
|
|
||||||
await db.insert(schema.settings).values([
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'booking_window_days',
|
|
||||||
value: '7',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'max_booking_duration_hours',
|
|
||||||
value: '2',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'min_booking_duration_minutes',
|
|
||||||
value: '30',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'booking_start_time',
|
|
||||||
value: '08:00',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'booking_end_time',
|
|
||||||
value: '22:00',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
key: 'allow_weekend_bookings',
|
|
||||||
value: 'true',
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Create time slots for all days (8 AM to 10 PM)
|
|
||||||
const timeSlotData = [];
|
|
||||||
for (let day = 0; day < 7; day++) {
|
|
||||||
for (let hour = 8; hour < 22; hour += 2) {
|
|
||||||
timeSlotData.push({
|
|
||||||
id: randomUUID(),
|
|
||||||
dayOfWeek: day,
|
|
||||||
startTime: `${hour.toString().padStart(2, '0')}:00`,
|
|
||||||
endTime: `${(hour + 2).toString().padStart(2, '0')}:00`,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.insert(schema.timeSlots).values(timeSlotData);
|
|
||||||
|
|
||||||
// Create sample announcement
|
|
||||||
await db.insert(schema.announcements).values({
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'Welcome to Table Tennis Booking System',
|
|
||||||
content: 'Book your court times easily and manage your games efficiently.',
|
|
||||||
isActive: true,
|
|
||||||
priority: 'high',
|
|
||||||
expiresAt: null,
|
|
||||||
createdAt: new Date(now),
|
|
||||||
updatedAt: new Date(now),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Seed data inserted successfully!');
|
|
||||||
console.log('Database reset complete!');
|
|
||||||
|
|
||||||
sqlite.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDatabase().catch(console.error);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { db } from '@/lib/db';
|
|
||||||
import { announcements } from '@/lib/db/schema';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
|
|
||||||
async function seedAnnouncements() {
|
|
||||||
try {
|
|
||||||
const testAnnouncements = [
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'Welcome to the New Booking System!',
|
|
||||||
content:
|
|
||||||
'We have upgraded our table tennis booking system with new features including mobile support, partner booking, and booking management. Enjoy your games!',
|
|
||||||
priority: 'high' as const,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'Court Maintenance Schedule',
|
|
||||||
content:
|
|
||||||
'Court 2 will be under maintenance this Friday from 2 PM to 4 PM. Please plan your bookings accordingly.',
|
|
||||||
priority: 'medium' as const,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'New Partnership Feature',
|
|
||||||
content:
|
|
||||||
'You can now specify your playing partner when making a booking. This helps other players know who will be using the court.',
|
|
||||||
priority: 'low' as const,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const announcement of testAnnouncements) {
|
|
||||||
await db.insert(announcements).values(announcement);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Test announcements created successfully!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating test announcements:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seedAnnouncements();
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
import { db } from '../lib/db';
|
|
||||||
import { users, courts as courtsTable, bookings, announcements, activityLogs } from '../lib/db/schema';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import bcrypt from 'bcryptjs';
|
|
||||||
|
|
||||||
async function seedData() {
|
|
||||||
try {
|
|
||||||
console.log('Starting data seeding...');
|
|
||||||
|
|
||||||
// Get existing users to add sample bookings and activities
|
|
||||||
const existingUsers = await db.select().from(users);
|
|
||||||
|
|
||||||
if (existingUsers.length < 2) {
|
|
||||||
console.log('Not enough users found. Please run the reset-database script first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminUser = existingUsers.find((u) => u.role === 'admin');
|
|
||||||
const regularUser = existingUsers.find((u) => u.role === 'user');
|
|
||||||
const courts = await db.select().from(courtsTable);
|
|
||||||
|
|
||||||
if (!adminUser || !regularUser || courts.length === 0) {
|
|
||||||
console.log('Missing admin user, regular user, or courts. Please run reset-database first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const today = now.toISOString().split('T')[0];
|
|
||||||
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
||||||
|
|
||||||
// Add some sample bookings
|
|
||||||
console.log('Creating sample bookings...');
|
|
||||||
|
|
||||||
const sampleBookings = [
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: regularUser.id,
|
|
||||||
courtId: courts[0].id,
|
|
||||||
date: today,
|
|
||||||
startTime: '19:00',
|
|
||||||
endTime: '20:00',
|
|
||||||
status: 'active' as const,
|
|
||||||
notes: 'Regular evening practice session',
|
|
||||||
createdAt: new Date(now.getTime() - 2 * 60 * 60 * 1000), // 2 hours ago
|
|
||||||
updatedAt: new Date(now.getTime() - 2 * 60 * 60 * 1000),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: regularUser.id,
|
|
||||||
courtId: courts[1] ? courts[1].id : courts[0].id,
|
|
||||||
date: tomorrow,
|
|
||||||
startTime: '20:00',
|
|
||||||
endTime: '21:00',
|
|
||||||
status: 'active' as const,
|
|
||||||
notes: 'Tournament preparation',
|
|
||||||
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
|
|
||||||
updatedAt: new Date(now.getTime() - 1 * 60 * 60 * 1000),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await db.insert(bookings).values(sampleBookings);
|
|
||||||
|
|
||||||
// Add sample activity logs
|
|
||||||
console.log('Creating sample activity logs...');
|
|
||||||
|
|
||||||
const sampleLogs = [
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: adminUser.id,
|
|
||||||
action: 'login',
|
|
||||||
entityType: 'user',
|
|
||||||
entityId: adminUser.id,
|
|
||||||
details: JSON.stringify({
|
|
||||||
email: adminUser.email,
|
|
||||||
role: adminUser.role,
|
|
||||||
loginMethod: 'password',
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.100',
|
|
||||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
||||||
createdAt: new Date(now.getTime() - 3 * 60 * 60 * 1000), // 3 hours ago
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: regularUser.id,
|
|
||||||
action: 'create_booking',
|
|
||||||
entityType: 'booking',
|
|
||||||
entityId: sampleBookings[0].id,
|
|
||||||
details: JSON.stringify({
|
|
||||||
courtId: courts[0].id,
|
|
||||||
courtName: courts[0].name,
|
|
||||||
date: today,
|
|
||||||
startTime: '19:00',
|
|
||||||
endTime: '20:00',
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.101',
|
|
||||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
|
|
||||||
createdAt: new Date(now.getTime() - 2 * 60 * 60 * 1000), // 2 hours ago
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: regularUser.id,
|
|
||||||
action: 'login',
|
|
||||||
entityType: 'user',
|
|
||||||
entityId: regularUser.id,
|
|
||||||
details: JSON.stringify({
|
|
||||||
email: regularUser.email,
|
|
||||||
role: regularUser.role,
|
|
||||||
loginMethod: 'password',
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.101',
|
|
||||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
|
|
||||||
createdAt: new Date(now.getTime() - 2.5 * 60 * 60 * 1000), // 2.5 hours ago
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: adminUser.id,
|
|
||||||
action: 'create_announcement',
|
|
||||||
entityType: 'announcement',
|
|
||||||
entityId: null,
|
|
||||||
details: JSON.stringify({
|
|
||||||
title: 'System Maintenance',
|
|
||||||
priority: 'high',
|
|
||||||
action: 'created_via_seed',
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.100',
|
|
||||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
||||||
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: regularUser.id,
|
|
||||||
action: 'create_booking',
|
|
||||||
entityType: 'booking',
|
|
||||||
entityId: sampleBookings[1].id,
|
|
||||||
details: JSON.stringify({
|
|
||||||
courtId: courts[1] ? courts[1].id : courts[0].id,
|
|
||||||
courtName: courts[1] ? courts[1].name : courts[0].name,
|
|
||||||
date: tomorrow,
|
|
||||||
startTime: '20:00',
|
|
||||||
endTime: '21:00',
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.101',
|
|
||||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
|
|
||||||
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
userId: adminUser.id,
|
|
||||||
action: 'update_settings',
|
|
||||||
entityType: 'settings',
|
|
||||||
entityId: null,
|
|
||||||
details: JSON.stringify({
|
|
||||||
changedSettings: ['booking_window_days', 'max_booking_duration_hours'],
|
|
||||||
previousValues: { booking_window_days: '7', max_booking_duration_hours: '2' },
|
|
||||||
newValues: { booking_window_days: '14', max_booking_duration_hours: '3' },
|
|
||||||
}),
|
|
||||||
ipAddress: '192.168.1.100',
|
|
||||||
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
||||||
createdAt: new Date(now.getTime() - 30 * 60 * 1000), // 30 minutes ago
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await db.insert(activityLogs).values(sampleLogs);
|
|
||||||
|
|
||||||
// Add more announcements for testing
|
|
||||||
console.log('Creating additional announcements...');
|
|
||||||
|
|
||||||
const additionalAnnouncements = [
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'New Court Rules',
|
|
||||||
content: 'Please remember to clean up after your sessions and respect the time limits.',
|
|
||||||
isActive: true,
|
|
||||||
priority: 'medium' as const,
|
|
||||||
expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000), // 1 week from now
|
|
||||||
createdAt: new Date(now.getTime() - 4 * 60 * 60 * 1000), // 4 hours ago
|
|
||||||
updatedAt: new Date(now.getTime() - 4 * 60 * 60 * 1000),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: randomUUID(),
|
|
||||||
title: 'Tournament Sign-ups Open',
|
|
||||||
content: 'The annual table tennis tournament sign-ups are now open! Register by the end of this month.',
|
|
||||||
isActive: true,
|
|
||||||
priority: 'high' as const,
|
|
||||||
expiresAt: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
|
|
||||||
createdAt: new Date(now.getTime() - 24 * 60 * 60 * 1000), // 1 day ago
|
|
||||||
updatedAt: new Date(now.getTime() - 24 * 60 * 60 * 1000),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await db.insert(announcements).values(additionalAnnouncements);
|
|
||||||
|
|
||||||
console.log('Sample data seeding completed successfully!');
|
|
||||||
console.log(`Created:
|
|
||||||
- ${sampleBookings.length} sample bookings
|
|
||||||
- ${sampleLogs.length} activity logs
|
|
||||||
- ${additionalAnnouncements.length} additional announcements`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error seeding data:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seedData();
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { db } from '../lib/db';
|
|
||||||
import { timeSlots } from '../lib/db/schema';
|
|
||||||
|
|
||||||
async function seedTimeSlots() {
|
|
||||||
console.log('🌱 Seeding time slots...');
|
|
||||||
|
|
||||||
// Example time slots for different days
|
|
||||||
const timeSlotData = [
|
|
||||||
// Sunday: 12:00 - 17:00
|
|
||||||
{ dayOfWeek: 0, startTime: '12:00', endTime: '17:00' },
|
|
||||||
|
|
||||||
// Monday: 19:00 - 23:00
|
|
||||||
{ dayOfWeek: 1, startTime: '19:00', endTime: '23:00' },
|
|
||||||
|
|
||||||
// Tuesday: 19:00 - 23:00
|
|
||||||
{ dayOfWeek: 2, startTime: '19:00', endTime: '23:00' },
|
|
||||||
|
|
||||||
// Wednesday: NO SLOTS (facility closed)
|
|
||||||
// { dayOfWeek: 3, startTime: '18:00', endTime: '22:00' },
|
|
||||||
|
|
||||||
// Thursday: NO SLOTS (facility closed)
|
|
||||||
// { dayOfWeek: 4, startTime: '19:00', endTime: '23:00' },
|
|
||||||
|
|
||||||
// Friday: 18:00 - 22:00
|
|
||||||
{ dayOfWeek: 5, startTime: '18:00', endTime: '22:00' },
|
|
||||||
|
|
||||||
// Saturday: 10:00 - 18:00
|
|
||||||
{ dayOfWeek: 6, startTime: '10:00', endTime: '18:00' },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const slot of timeSlotData) {
|
|
||||||
await db.insert(timeSlots).values({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
dayOfWeek: slot.dayOfWeek,
|
|
||||||
startTime: slot.startTime,
|
|
||||||
endTime: slot.endTime,
|
|
||||||
isActive: true,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✅ Time slots seeding completed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the seeding function
|
|
||||||
seedTimeSlots()
|
|
||||||
.then(() => {
|
|
||||||
console.log('Time slots seeding process completed');
|
|
||||||
process.exit(0);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error during time slots seeding:', error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
+2
-1
@@ -3,7 +3,8 @@ import { drizzle } from 'drizzle-orm/better-sqlite3';
|
|||||||
import * as schema from '../lib/db/schema';
|
import * as schema from '../lib/db/schema';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
const sqlite = new Database('./sqlite.db');
|
const dbPath = process.env.DATABASE_URL || './data/sqlite.db';
|
||||||
|
const sqlite = new Database(dbPath);
|
||||||
const db = drizzle(sqlite, { schema });
|
const db = drizzle(sqlite, { schema });
|
||||||
|
|
||||||
interface ResetOptions {
|
interface ResetOptions {
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ async function printDatabaseSummary() {
|
|||||||
console.log(`\n⚙️ Settings: ${settings.length} configured`);
|
console.log(`\n⚙️ Settings: ${settings.length} configured`);
|
||||||
|
|
||||||
console.log('\n💡 Login Credentials:');
|
console.log('\n💡 Login Credentials:');
|
||||||
console.log(' Admin: admin@tabletennis.com / admin123');
|
console.log(` Admin: ${process.env.ADMIN_EMAIL || 'admin@tabletennis.com'} / ${process.env.ADMIN_PASSWORD || 'admin123'}`);
|
||||||
console.log(' User: user@tabletennis.com / user123');
|
console.log(' User: user@tabletennis.com / user123');
|
||||||
|
|
||||||
console.log('\n🚀 Ready to start! Run: npm run dev');
|
console.log('\n🚀 Ready to start! Run: npm run dev');
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user