diff --git a/.env.example b/.env.example index e61504f..ee4d92e 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,33 @@ -# Database -DATABASE_URL="./sqlite.db" +# Environment Configuration Template +# Copy this to .env.production and fill in your values -# NextAuth.js -NEXTAUTH_URL="http://localhost:3000" -NEXTAUTH_SECRET="your-secret-key-here-make-this-very-long-and-random" +# === REQUIRED VARIABLES === +# Database URL (host path - gets mounted into container) +DATABASE_URL=./data/sqlite.db -# Email Configuration (Gmail) -EMAIL_USER="your-email@gmail.com" -EMAIL_PASSWORD="your-app-password-here" +# NextAuth.js Configuration (REQUIRED) +NEXTAUTH_URL=https://your-domain.com +NEXTAUTH_SECRET=your-long-random-secret-here-generate-with-openssl-rand-base64-32 -# Admin Configuration -ADMIN_EMAIL="admin@example.com" -ADMIN_PASSWORD="admin123" +# Admin User (CHANGE THESE!) +ADMIN_EMAIL=admin@your-domain.com +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 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c05c5bd..0000000 --- a/Dockerfile +++ /dev/null @@ -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"] diff --git a/Dockerfile.production b/Dockerfile.production index 392caef..446d4bf 100644 --- a/Dockerfile.production +++ b/Dockerfile.production @@ -54,14 +54,21 @@ COPY --chown=nextjs:nodejs </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..." exec node server.js EOF RUN chmod +x /app/start.sh - -USER nextjs - EXPOSE 3000 ENV PORT=3000 diff --git a/PRODUCTION_README.md b/PRODUCTION_README.md deleted file mode 100644 index 9b9623b..0000000 --- a/PRODUCTION_README.md +++ /dev/null @@ -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! ๐Ÿ“ diff --git a/deploy.sh b/deploy.sh index 76e2493..9d76662 100755 --- a/deploy.sh +++ b/deploy.sh @@ -32,14 +32,21 @@ fi echo "๐Ÿ› ๏ธ Setting up the database..." 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 echo "๐Ÿณ Building and starting Docker 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 -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 echo "โณ Waiting for containers to be healthy..." @@ -48,13 +55,13 @@ sleep 30 # Check health echo "๐Ÿ” Checking application health..." 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!" break elif [ $i -eq 10 ]; then echo "โŒ Application health check failed after 10 attempts" 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 else echo "โณ Attempt $i/10: Application not ready yet, waiting..." @@ -64,24 +71,24 @@ done # Show running containers echo "๐Ÿ“Š Running containers:" -docker-compose -f docker-compose.production.yml ps +docker compose -f docker-compose.production.yml ps # Show 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 "๐ŸŽ‰ Deployment completed successfully!" echo "" echo "๐Ÿ“Š Application Status:" echo " โ€ข URL: https://lcc-tt-booking.mikicvi.com" -echo " โ€ข Health Check: http://localhost:3000/api/health" -echo " โ€ข Container Status: $(docker-compose -f docker-compose.production.yml ps -q tt-booking | xargs docker inspect -f '{{.State.Status}}')" +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 "" echo "๐Ÿ”ง Useful commands:" -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 " โ€ข Stop: docker-compose -f docker-compose.production.yml down" +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 " โ€ข Stop: docker compose -f docker-compose.production.yml down" echo "" echo "โš ๏ธ Don't forget to:" echo " 1. Set up Cloudflare Tunnel to expose your application" diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 75ffdf1..b94e4cd 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -1,26 +1,17 @@ -version: '3.8' - services: tt-booking: build: context: . - dockerfile: Dockerfile + dockerfile: Dockerfile.production container_name: lcc-tt-booking + user: "1000:1000" ports: - - '3000:3000' + - '3036:3000' + env_file: + - .env.production environment: - - NODE_ENV=production - - DATABASE_URL=/app/data/sqlite.db - - NEXTAUTH_URL=https://lcc-tt-booking.mikicvi.com - - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - - EMAIL_USER=${EMAIL_USER} - - EMAIL_PASSWORD=${EMAIL_PASSWORD} - - ADMIN_EMAIL=${ADMIN_EMAIL} - - ADMIN_PASSWORD=${ADMIN_PASSWORD} + # Container-specific override - PORT=3000 - - RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100} - - RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-900000} - - LOG_LEVEL=${LOG_LEVEL:-info} volumes: - ./data:/app/data - ./backups:/app/backups diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 84904c4..0000000 --- a/docker-compose.yml +++ /dev/null @@ -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 diff --git a/lib/session.ts b/lib/session.ts index d8813a2..364479d 100644 --- a/lib/session.ts +++ b/lib/session.ts @@ -49,7 +49,7 @@ export async function createSession(payload: Omit) const cookieStore = await cookies(); cookieStore.set('session', session, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', + secure: process.env.NODE_ENV === 'production' && process.env.NEXTAUTH_URL?.startsWith('https'), expires: expiresAt, sameSite: 'lax', path: '/', @@ -70,7 +70,7 @@ export async function updateSession() { cookieStore.set('session', newSession, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', + secure: process.env.NODE_ENV === 'production' && process.env.NEXTAUTH_URL?.startsWith('https'), expires: expires, sameSite: 'lax', path: '/', diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index cc617be..0000000 --- a/nginx.conf +++ /dev/null @@ -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; - } - } -} diff --git a/scripts/cleanup-old-seeds.sh b/scripts/cleanup-old-seeds.sh deleted file mode 100755 index 33a9c3b..0000000 --- a/scripts/cleanup-old-seeds.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/scripts/old-seeds/README.md b/scripts/old-seeds/README.md deleted file mode 100644 index 8d9c1b8..0000000 --- a/scripts/old-seeds/README.md +++ /dev/null @@ -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. diff --git a/scripts/old-seeds/init-admin-data.ts b/scripts/old-seeds/init-admin-data.ts deleted file mode 100644 index 1cafc81..0000000 --- a/scripts/old-seeds/init-admin-data.ts +++ /dev/null @@ -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(); diff --git a/scripts/old-seeds/reset-database.ts b/scripts/old-seeds/reset-database.ts deleted file mode 100644 index 7dc7915..0000000 --- a/scripts/old-seeds/reset-database.ts +++ /dev/null @@ -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); diff --git a/scripts/old-seeds/seed-announcements.ts b/scripts/old-seeds/seed-announcements.ts deleted file mode 100644 index b9b6453..0000000 --- a/scripts/old-seeds/seed-announcements.ts +++ /dev/null @@ -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(); diff --git a/scripts/old-seeds/seed-data.ts b/scripts/old-seeds/seed-data.ts deleted file mode 100644 index 7bf2f8a..0000000 --- a/scripts/old-seeds/seed-data.ts +++ /dev/null @@ -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(); diff --git a/scripts/old-seeds/seed-time-slots.ts b/scripts/old-seeds/seed-time-slots.ts deleted file mode 100644 index 2fd5c76..0000000 --- a/scripts/old-seeds/seed-time-slots.ts +++ /dev/null @@ -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); - }); diff --git a/scripts/reset-db.ts b/scripts/reset-db.ts index 5c8c98a..dc14ebf 100644 --- a/scripts/reset-db.ts +++ b/scripts/reset-db.ts @@ -3,7 +3,8 @@ import { drizzle } from 'drizzle-orm/better-sqlite3'; import * as schema from '../lib/db/schema'; 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 }); interface ResetOptions { diff --git a/scripts/setup-database.ts b/scripts/setup-database.ts index b874674..39ee934 100644 --- a/scripts/setup-database.ts +++ b/scripts/setup-database.ts @@ -598,7 +598,7 @@ async function printDatabaseSummary() { console.log(`\nโš™๏ธ Settings: ${settings.length} configured`); 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('\n๐Ÿš€ Ready to start! Run: npm run dev'); diff --git a/sqlite.db.backup b/sqlite.db.backup deleted file mode 100644 index 4657d0c..0000000 Binary files a/sqlite.db.backup and /dev/null differ