Refactor production setup and database management
- Updated Dockerfile for production to ensure proper database permissions and improved startup script. - Removed outdated PRODUCTION_README.md and consolidated relevant information into other documentation. - Enhanced deploy.sh script to fix database permissions and streamline deployment process. - Modified docker-compose configuration to use a dedicated production file and adjusted port mappings. - Removed legacy docker-compose.yml file to avoid confusion. - Improved session management by refining secure cookie settings based on environment variables. - Deleted obsolete Nginx configuration and old seed scripts to clean up the project structure. - Updated database setup scripts to reflect new structure and removed old seed data scripts. - Adjusted reset-db script to use environment variable for database path. - Enhanced setup-database script to provide dynamic admin credentials in the summary. - Removed unnecessary backup file for SQLite database.
This commit is contained in:
+30
-11
@@ -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
|
||||
|
||||
-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
|
||||
set -e
|
||||
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..."
|
||||
exec node server.js
|
||||
EOF
|
||||
|
||||
RUN chmod +x /app/start.sh
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 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..."
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
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: '/',
|
||||
|
||||
-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 { 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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user