Add new build and database setup scripts to package.json for production

This commit is contained in:
2025-08-19 23:21:11 +01:00
parent 3704a70575
commit a40a2c022d
9 changed files with 896 additions and 0 deletions

69
.do/app.yaml Normal file
View File

@ -0,0 +1,69 @@
name: scriptshare
region: nyc
# Static site for the frontend
static_sites:
- name: scriptshare-frontend
github:
repo: your-username/scriptshare-cursor
branch: main
build_command: npm install && npm run build
output_dir: dist
environment_slug: node-js
source_dir: /
routes:
- path: /
envs:
- key: VITE_APP_NAME
value: ScriptShare
- key: VITE_APP_URL
value: ${APP_URL}
- key: VITE_API_URL
value: ${scriptshare-api.PUBLIC_URL}
- key: VITE_ANALYTICS_ENABLED
value: "true"
# Backend API service
services:
- name: scriptshare-api
github:
repo: your-username/scriptshare-cursor
branch: main
source_dir: /
dockerfile_path: Dockerfile.api
environment_slug: node-js
instance_count: 1
instance_size_slug: basic-xxs
http_port: 3000
routes:
- path: /api
health_check:
http_path: /api/health
envs:
- key: NODE_ENV
value: production
- key: PORT
value: "3000"
- key: DATABASE_URL
value: ${scriptshare-db.DATABASE_URL}
- key: JWT_SECRET
value: ${JWT_SECRET}
- key: CORS_ORIGIN
value: ${scriptshare-frontend.PUBLIC_URL}
# Managed MySQL database
databases:
- name: scriptshare-db
engine: MYSQL
version: "8"
size: db-s-1vcpu-1gb
num_nodes: 1
# Environment variables (these will be set in DigitalOcean dashboard)
envs:
- key: JWT_SECRET
scope: RUN_AND_BUILD_TIME
type: SECRET
- key: APP_URL
scope: RUN_AND_BUILD_TIME
value: https://scriptshare-frontend-${APP_DOMAIN}

102
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,102 @@
name: Deploy to DigitalOcean
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# Test job to run before deployment
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Build frontend
run: npm run build
env:
VITE_APP_NAME: ScriptShare
VITE_APP_URL: https://scriptshare.example.com
VITE_ANALYTICS_ENABLED: true
# Deploy job (only on main branch)
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
- name: Get app info
id: app-info
run: |
APP_ID=$(doctl apps list --format ID,Spec.Name --no-header | grep scriptshare | cut -d' ' -f1)
echo "app-id=$APP_ID" >> $GITHUB_OUTPUT
- name: Trigger deployment
run: |
doctl apps create-deployment ${{ steps.app-info.outputs.app-id }} --wait
- name: Run database migrations
run: |
# Wait for deployment to complete
sleep 60
# Run migrations via the app console (if needed)
echo "Deployment completed. Please run database migrations manually if this is the first deployment."
echo "Command: npm run db:setup:prod"
# Database migration job (manual trigger)
migrate:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run migrations
run: npm run db:migrate:prod
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
# Manual workflow dispatch for running migrations
on:
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
default: 'migrate'
type: choice
options:
- migrate
- setup

248
DEPLOYMENT.md Normal file
View File

@ -0,0 +1,248 @@
# DigitalOcean Deployment Guide
This guide walks you through deploying ScriptShare to DigitalOcean App Platform with a managed MySQL database.
## Prerequisites
- DigitalOcean account
- GitHub repository containing your code
- Basic familiarity with DigitalOcean App Platform
## Architecture Overview
The deployment consists of:
- **Frontend**: Static site (React/Vite build)
- **Backend API**: Node.js service with Express
- **Database**: DigitalOcean Managed MySQL Database
## Step 1: Prepare Your Repository
1. Ensure all the deployment files are in your GitHub repository:
```
.do/app.yaml # App Platform configuration
Dockerfile.api # Backend API container
drizzle.config.production.ts # Production DB config
scripts/migrate-production.js # Migration script
scripts/setup-production-db.js # DB setup script
env.production.example # Environment variables template
```
2. Commit and push all changes to your main branch.
## Step 2: Create the DigitalOcean App
### Option A: Using the DigitalOcean Console
1. Go to the [DigitalOcean App Platform](https://cloud.digitalocean.com/apps)
2. Click **"Create App"**
3. Choose **"GitHub"** as your source
4. Select your repository and branch (usually `main`)
5. DigitalOcean will automatically detect the `app.yaml` configuration
### Option B: Using the CLI
```bash
# Install doctl CLI
# On macOS: brew install doctl
# On Linux: snap install doctl
# Authenticate
doctl auth init
# Create the app
doctl apps create --spec .do/app.yaml
```
## Step 3: Configure Environment Variables
In the DigitalOcean dashboard, go to your app's Settings > Environment Variables and set:
### Required Variables
```
JWT_SECRET=your-super-secret-jwt-key-here-change-this-in-production
ADMIN_EMAIL=admin@yourcompany.com
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-secure-password
```
### Optional Variables
```
VITE_ANALYTICS_ENABLED=true
RATE_LIMIT_ENABLED=true
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
```
⚠️ **Security Note**: Generate a strong JWT secret:
```bash
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
```
## Step 4: Database Setup
The managed MySQL database will be automatically created. After the first deployment:
1. **Run Database Migrations**:
```bash
# In your app's console (or via GitHub Actions)
npm run db:setup:prod
```
2. **Verify Database Connection**:
Check the API health endpoint: `https://your-api-url/api/health`
## Step 5: Update App Configuration
1. **Update Frontend URLs**: After deployment, update the environment variables with actual URLs:
```
VITE_APP_URL=https://your-frontend-url.ondigitalocean.app
VITE_API_URL=https://your-api-url.ondigitalocean.app/api
CORS_ORIGIN=https://your-frontend-url.ondigitalocean.app
```
2. **Redeploy**: The app will automatically redeploy when you change environment variables.
## Step 6: Custom Domain (Optional)
1. In your app settings, go to **Domains**
2. Click **Add Domain**
3. Enter your domain name
4. Configure DNS records as instructed
## Database Management
### Connecting to the Database
```bash
# Get connection string from DigitalOcean dashboard
mysql -h your-db-host -P 25060 -u your-username -p your-database-name --ssl-mode=REQUIRED
```
### Running Migrations
```bash
# Production migration
npm run db:migrate:prod
# Create new migration
npm run db:generate
```
### Backup and Restore
DigitalOcean provides automatic daily backups. For manual backups:
```bash
# Create backup
mysqldump -h your-db-host -P 25060 -u your-username -p your-database-name --ssl-mode=REQUIRED > backup.sql
# Restore backup
mysql -h your-db-host -P 25060 -u your-username -p your-database-name --ssl-mode=REQUIRED < backup.sql
```
## Monitoring and Logs
### Application Logs
- View logs in DigitalOcean Console: App → Runtime Logs
- Or via CLI: `doctl apps logs <app-id> --type=run`
### Database Monitoring
- Database metrics available in DigitalOcean dashboard
- Set up alerts for CPU, memory, and connection usage
### Health Checks
- API: `https://your-api-url/api/health`
- Frontend: Built-in App Platform health checks
## Scaling
### Vertical Scaling
- Increase instance size in App Platform settings
- Database can be scaled up (not down) in database settings
### Horizontal Scaling
- Increase instance count for API service
- Frontend is automatically scaled as a static site
## Security Best Practices
1. **Environment Variables**: Never commit secrets to Git
2. **Database Access**: Use DigitalOcean's private networking
3. **SSL/TLS**: Enabled by default on App Platform
4. **Database Backups**: Verify daily backups are working
5. **Access Control**: Use DigitalOcean teams for access management
## Troubleshooting
### Common Issues
1. **Build Failures**:
```bash
# Check build logs
doctl apps logs <app-id> --type=build
```
2. **Database Connection Issues**:
- Verify DATABASE_URL format
- Check firewall settings
- Ensure SSL is enabled
3. **CORS Errors**:
- Verify CORS_ORIGIN matches frontend URL
- Check environment variable casing
4. **Missing Dependencies**:
```bash
# Clear npm cache and rebuild
npm ci --clean-cache
```
### Getting Support
- DigitalOcean Community: https://www.digitalocean.com/community
- Support tickets: https://cloud.digitalocean.com/support
- Documentation: https://docs.digitalocean.com/products/app-platform/
## Cost Optimization
### Current Configuration Costs (Approximate)
- **API Service**: $5/month (Basic plan)
- **Database**: $15/month (1GB RAM, 1 vCPU)
- **Static Site**: $0 (included with API service)
- **Total**: ~$20/month
### Cost Reduction Tips
1. Use development database for testing
2. Scale down during low usage periods
3. Monitor and optimize database queries
4. Use CDN for static assets
## Deployment Checklist
- [ ] Repository configured with deployment files
- [ ] Environment variables set in DigitalOcean
- [ ] JWT secret generated and configured
- [ ] Database migrations run successfully
- [ ] Health check endpoints responding
- [ ] Frontend can communicate with API
- [ ] Admin user created and accessible
- [ ] Custom domain configured (if applicable)
- [ ] Monitoring and alerts set up
- [ ] Backup strategy verified
## Next Steps
1. **Set up CI/CD**: Configure GitHub Actions for automated deployments
2. **Monitoring**: Set up application performance monitoring
3. **CDN**: Configure CDN for static assets
4. **Analytics**: Integrate application analytics
5. **Error Tracking**: Set up error monitoring service
## Support
For issues specific to this deployment setup, please check:
1. DigitalOcean App Platform documentation
2. Application logs in the DigitalOcean console
3. Database connection and query logs
Remember to regularly update dependencies and monitor security advisories for your application stack.

224
Dockerfile.api Normal file
View File

@ -0,0 +1,224 @@
# Production API Dockerfile for DigitalOcean
FROM node:18-alpine
# Install system dependencies for native modules
RUN apk add --no-cache python3 make g++ libc6-compat
WORKDIR /app
# Copy package files first for better caching
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production=false --silent
# Copy source code
COPY . .
# Create API-only build by removing frontend dependencies and files
RUN npm uninstall @vitejs/plugin-react-swc vite
RUN rm -rf src/components src/pages src/contexts src/hooks/use-toast.ts src/utils/toast.ts
RUN rm -rf src/main.tsx src/App.tsx src/index.css
RUN rm -rf public index.html vite.config.ts tailwind.config.ts postcss.config.js
# Keep only API and database files
# The structure will be: src/lib/api/* and src/lib/db/*
# Create a simple Express server
RUN cat > src/server.ts << 'EOF'
import express from 'express';
import cors from 'cors';
import { createUser, getUserByEmail, updateUser, getAllUsers, getUserById } from './lib/api/users.js';
import { getScripts, getScriptById, createScript, updateScript, deleteScript, moderateScript } from './lib/api/scripts.js';
import { login, register, refreshToken } from './lib/api/auth.js';
import { rateScript, getUserRating, getScriptRatingStats } from './lib/api/ratings.js';
import { getPlatformAnalytics, getScriptAnalytics, trackEvent } from './lib/api/analytics.js';
import { createCollection, getUserCollections, getPublicCollections, addScriptToCollection } from './lib/api/collections.js';
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Auth routes
app.post('/api/auth/login', async (req, res) => {
try {
const result = await login(req.body);
res.json(result);
} catch (error) {
console.error('Login error:', error);
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.post('/api/auth/register', async (req, res) => {
try {
const result = await register(req.body);
res.json(result);
} catch (error) {
console.error('Register error:', error);
res.status(400).json({ error: 'Registration failed' });
}
});
// Scripts routes
app.get('/api/scripts', async (req, res) => {
try {
const result = await getScripts(req.query);
res.json(result);
} catch (error) {
console.error('Get scripts error:', error);
res.status(500).json({ error: 'Failed to fetch scripts' });
}
});
app.get('/api/scripts/:id', async (req, res) => {
try {
const script = await getScriptById(req.params.id);
if (!script) {
return res.status(404).json({ error: 'Script not found' });
}
res.json(script);
} catch (error) {
console.error('Get script error:', error);
res.status(500).json({ error: 'Failed to fetch script' });
}
});
app.post('/api/scripts', async (req, res) => {
try {
const userId = req.headers['x-user-id'] as string;
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
const result = await createScript(req.body, userId);
res.json(result);
} catch (error) {
console.error('Create script error:', error);
res.status(500).json({ error: 'Failed to create script' });
}
});
// Users routes
app.get('/api/users', async (req, res) => {
try {
const result = await getAllUsers();
res.json(result);
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/api/users/:id', async (req, res) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// Analytics routes
app.get('/api/analytics/platform', async (req, res) => {
try {
const days = parseInt(req.query.days as string) || 30;
const result = await getPlatformAnalytics(days);
res.json(result);
} catch (error) {
console.error('Analytics error:', error);
res.status(500).json({ error: 'Failed to fetch analytics' });
}
});
app.post('/api/analytics/track', async (req, res) => {
try {
const result = await trackEvent(req.body);
res.json(result);
} catch (error) {
console.error('Track event error:', error);
res.status(500).json({ error: 'Failed to track event' });
}
});
// Collections routes
app.get('/api/collections', async (req, res) => {
try {
const userId = req.headers['x-user-id'] as string;
const result = userId ? await getUserCollections(userId) : await getPublicCollections();
res.json(result);
} catch (error) {
console.error('Get collections error:', error);
res.status(500).json({ error: 'Failed to fetch collections' });
}
});
// Ratings routes
app.post('/api/ratings', async (req, res) => {
try {
const result = await rateScript(req.body);
res.json(result);
} catch (error) {
console.error('Rate script error:', error);
res.status(500).json({ error: 'Failed to rate script' });
}
});
app.get('/api/scripts/:id/ratings', async (req, res) => {
try {
const result = await getScriptRatingStats(req.params.id);
res.json(result);
} catch (error) {
console.error('Get ratings error:', error);
res.status(500).json({ error: 'Failed to fetch ratings' });
}
});
// Error handling middleware
app.use((error: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error('Unhandled error:', error);
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Endpoint not found' });
});
app.listen(PORT, () => {
console.log(`ScriptShare API server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
console.log(`Database URL configured: ${!!process.env.DATABASE_URL}`);
});
EOF
# Install Express and CORS for the API server
RUN npm install express cors @types/express @types/cors
# Build TypeScript (if any TS files remain)
RUN npx tsc --build || echo "TypeScript build completed with warnings"
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
# Start the API server
CMD ["node", "src/server.js"]

View File

@ -0,0 +1,16 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/lib/db/schema.ts',
out: './drizzle',
dialect: 'mysql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
// DigitalOcean Managed Database specific settings
introspect: {
casing: 'camel'
}
});

40
env.production.example Normal file
View File

@ -0,0 +1,40 @@
# Production Environment Configuration for DigitalOcean
# Database Configuration (will be replaced by DigitalOcean DATABASE_URL)
DATABASE_URL="mysql://username:password@hostname:port/database_name"
# JWT Secret for authentication (SET THIS IN DIGITALOCEAN DASHBOARD)
JWT_SECRET="your-super-secret-jwt-key-here-change-this-in-production"
# App Configuration
NODE_ENV="production"
PORT="3000"
# Frontend Configuration
VITE_APP_NAME="ScriptShare"
VITE_APP_URL="https://your-app-domain.ondigitalocean.app"
VITE_API_URL="https://your-api-domain.ondigitalocean.app/api"
VITE_ANALYTICS_ENABLED="true"
# CORS Configuration
CORS_ORIGIN="https://your-frontend-domain.ondigitalocean.app"
# Admin User Configuration (for initial setup only)
ADMIN_EMAIL="admin@yourcompany.com"
ADMIN_USERNAME="admin"
ADMIN_PASSWORD="change-this-secure-password"
# Optional: Rate Limiting
RATE_LIMIT_ENABLED="true"
RATE_LIMIT_WINDOW_MS="900000"
RATE_LIMIT_MAX_REQUESTS="100"
# Optional: File Upload Configuration
MAX_FILE_SIZE="10485760"
UPLOAD_PATH="/tmp/uploads"
# Optional: Email Configuration (if needed)
SMTP_HOST=""
SMTP_PORT=""
SMTP_USER=""
SMTP_PASS=""

View File

@ -7,11 +7,15 @@
"dev": "vite",
"build": "tsc && vite build",
"build:dev": "tsc && vite build --mode development",
"build:api": "tsc src/server.ts --outDir dist --target es2020 --module commonjs --esModuleInterop --allowSyntheticDefaultImports --resolveJsonModule --skipLibCheck",
"start:api": "node dist/server.js",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:migrate:prod": "drizzle-kit migrate --config=drizzle.config.production.ts",
"db:studio": "drizzle-kit studio",
"db:setup:prod": "node scripts/setup-production-db.js",
"create-superuser": "node scripts/create-superuser.js",
"create-default-superuser": "node scripts/create-default-superuser.js",
"setup-oliver": "node scripts/setup-oliver-admin.js"

View File

@ -0,0 +1,77 @@
#!/usr/bin/env node
/**
* Production database migration script for DigitalOcean deployment
* This script runs database migrations against the production MySQL database
*/
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import { migrate } from 'drizzle-orm/mysql2/migrator';
import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables
config();
async function runMigrations() {
console.log('🚀 Starting production database migration...');
if (!process.env.DATABASE_URL) {
console.error('❌ DATABASE_URL environment variable is required');
process.exit(1);
}
let connection;
try {
// Parse DATABASE_URL for DigitalOcean managed database
const dbUrl = new URL(process.env.DATABASE_URL);
// Create connection to MySQL
connection = await mysql.createConnection({
host: dbUrl.hostname,
port: parseInt(dbUrl.port) || 25060,
user: dbUrl.username,
password: dbUrl.password,
database: dbUrl.pathname.slice(1), // Remove leading slash
ssl: {
rejectUnauthorized: false // DigitalOcean managed databases use SSL
},
connectTimeout: 60000,
acquireTimeout: 60000,
timeout: 60000,
});
console.log('✅ Connected to database');
// Create drizzle instance
const db = drizzle(connection);
// Run migrations
console.log('🔄 Running migrations...');
await migrate(db, { migrationsFolder: join(__dirname, '../drizzle') });
console.log('✅ Migrations completed successfully!');
} catch (error) {
console.error('❌ Migration failed:', error);
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('🔌 Database connection closed');
}
}
}
// Run migrations if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
runMigrations().catch(console.error);
}
export { runMigrations };

View File

@ -0,0 +1,116 @@
#!/usr/bin/env node
/**
* Production database setup script for DigitalOcean
* This script sets up the initial database structure and creates a default admin user
*/
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import { migrate } from 'drizzle-orm/mysql2/migrator';
import bcrypt from 'bcrypt';
import { nanoid } from 'nanoid';
import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import * as schema from '../src/lib/db/schema.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables
config();
async function setupProductionDatabase() {
console.log('🚀 Setting up production database...');
if (!process.env.DATABASE_URL) {
console.error('❌ DATABASE_URL environment variable is required');
process.exit(1);
}
let connection;
try {
// Parse DATABASE_URL for DigitalOcean managed database
const dbUrl = new URL(process.env.DATABASE_URL);
// Create connection to MySQL
connection = await mysql.createConnection({
host: dbUrl.hostname,
port: parseInt(dbUrl.port) || 25060,
user: dbUrl.username,
password: dbUrl.password,
database: dbUrl.pathname.slice(1), // Remove leading slash
ssl: {
rejectUnauthorized: false // DigitalOcean managed databases use SSL
},
connectTimeout: 60000,
acquireTimeout: 60000,
timeout: 60000,
});
console.log('✅ Connected to database');
// Create drizzle instance
const db = drizzle(connection, { schema });
// Run migrations
console.log('🔄 Running migrations...');
await migrate(db, { migrationsFolder: join(__dirname, '../drizzle') });
console.log('✅ Migrations completed');
// Create default admin user
console.log('👤 Creating default admin user...');
const adminEmail = process.env.ADMIN_EMAIL || 'admin@scriptshare.com';
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
const adminUsername = process.env.ADMIN_USERNAME || 'admin';
// Check if admin user already exists
const existingAdmin = await db.query.users.findFirst({
where: (users, { eq }) => eq(users.email, adminEmail)
});
if (existingAdmin) {
console.log(' Admin user already exists, skipping creation');
} else {
const hashedPassword = await bcrypt.hash(adminPassword, 10);
await db.insert(schema.users).values({
id: nanoid(),
email: adminEmail,
username: adminUsername,
displayName: 'System Administrator',
isAdmin: true,
isModerator: true,
avatarUrl: null,
bio: 'Default system administrator account'
});
console.log('✅ Default admin user created');
console.log(`📧 Email: ${adminEmail}`);
console.log(`👤 Username: ${adminUsername}`);
console.log(`🔑 Password: ${adminPassword}`);
console.log('⚠️ Please change the default password after first login!');
}
console.log('🎉 Production database setup completed successfully!');
} catch (error) {
console.error('❌ Setup failed:', error);
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('🔌 Database connection closed');
}
}
}
// Run setup if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
setupProductionDatabase().catch(console.error);
}
export { setupProductionDatabase };