fully working when deployed
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
# Environment Configuration Template
|
|
||||||
# Copy this to .env.production and fill in your values
|
|
||||||
|
|
||||||
# === REQUIRED VARIABLES ===
|
|
||||||
# Database URL (host path - gets mounted into container)
|
|
||||||
DATABASE_URL=./data/sqlite.db
|
|
||||||
|
|
||||||
# NextAuth.js Configuration (REQUIRED)
|
|
||||||
NEXTAUTH_URL=https://your-domain.com
|
|
||||||
NEXTAUTH_SECRET=your-long-random-secret-here-generate-with-openssl-rand-base64-32
|
|
||||||
|
|
||||||
# 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
|
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
# Required Environment Variables for TT Booking
|
||||||
|
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
NEXTAUTH_SECRET=your-secret-key-here
|
||||||
|
|
||||||
|
# Your application URL
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Admin user credentials (change these!)
|
||||||
|
ADMIN_EMAIL=admin@your-domain.com
|
||||||
|
ADMIN_PASSWORD=your-secure-password
|
||||||
|
|
||||||
|
# Optional: Email configuration for notifications
|
||||||
|
EMAIL_USER=your-email@gmail.com
|
||||||
|
EMAIL_PASSWORD=your-gmail-app-password
|
||||||
|
|
||||||
|
# Optional: Rate limiting (defaults shown)
|
||||||
|
RATE_LIMIT_MAX=100
|
||||||
|
RATE_LIMIT_WINDOW=900000
|
||||||
|
|
||||||
|
# Optional: Logging
|
||||||
|
LOG_LEVEL=info
|
||||||
+17
-28
@@ -34,41 +34,29 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
# Create system user and group
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
|
||||||
RUN adduser --system --uid 1001 nextjs
|
|
||||||
|
|
||||||
# Copy necessary files from builder stage
|
# Copy necessary files from builder stage
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=root:root /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
# Copy full node_modules for database operations
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
|
||||||
|
# Copy database setup scripts and package.json
|
||||||
|
COPY --from=builder /app/scripts ./scripts
|
||||||
|
COPY --from=builder /app/lib ./lib
|
||||||
|
COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
|
||||||
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
# Create public directory if it doesn't exist
|
# Create public directory if it doesn't exist
|
||||||
RUN mkdir -p public
|
RUN mkdir -p public
|
||||||
|
|
||||||
# Create directories for data and backups
|
# Create directories for data and backups
|
||||||
RUN mkdir -p /app/data /app/backups /app/logs && \
|
RUN mkdir -p /app/data /app/backups /app/logs
|
||||||
chown -R nextjs:nodejs /app/data /app/backups /app/logs
|
|
||||||
|
|
||||||
# Create startup script
|
|
||||||
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
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
@@ -78,4 +66,5 @@ ENV HOSTNAME="0.0.0.0"
|
|||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||||
|
|
||||||
CMD ["/app/start.sh"]
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -28,18 +28,8 @@ if [ -d ".git" ]; then
|
|||||||
git pull origin main || echo "⚠️ Git pull failed or not needed"
|
git pull origin main || echo "⚠️ Git pull failed or not needed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Setup database
|
|
||||||
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
|
# Build and deploy with Docker Compose
|
||||||
|
echo "� Building and starting Docker containers..."
|
||||||
echo "🐳 Building and starting Docker containers..."
|
echo "🐳 Building and starting Docker containers..."
|
||||||
|
|
||||||
# Stop existing containers
|
# Stop existing containers
|
||||||
|
|||||||
@@ -4,17 +4,10 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.production
|
dockerfile: Dockerfile.production
|
||||||
container_name: lcc-tt-booking
|
container_name: lcc-tt-booking
|
||||||
user: "1000:1000"
|
|
||||||
ports:
|
ports:
|
||||||
- '3036:3000'
|
- '3036:3000'
|
||||||
env_file:
|
env_file:
|
||||||
- .env.production
|
- .env.production
|
||||||
- '3036:3000'
|
|
||||||
env_file:
|
|
||||||
- .env.production
|
|
||||||
environment:
|
|
||||||
# Container-specific override
|
|
||||||
- PORT=3000
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./backups:/app/backups
|
- ./backups:/app/backups
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# Sample Docker Compose for TT Booking
|
||||||
|
# Copy this file and create a .env file with your settings
|
||||||
|
|
||||||
|
services:
|
||||||
|
tt-booking:
|
||||||
|
image: your-registry/tt-booking:latest
|
||||||
|
container_name: tt-booking
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NEXTAUTH_URL=http://localhost:3000
|
||||||
|
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||||
|
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@tabletennis.com}
|
||||||
|
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
|
||||||
|
# Optional email settings
|
||||||
|
- EMAIL_USER=${EMAIL_USER}
|
||||||
|
- EMAIL_PASSWORD=${EMAIL_PASSWORD}
|
||||||
|
# Optional rate limiting
|
||||||
|
- RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100}
|
||||||
|
- RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-900000}
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
- ./backups:/app/backups
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# Optional: Automated backup service
|
||||||
|
backup:
|
||||||
|
image: alpine:latest
|
||||||
|
container_name: tt-booking-backup
|
||||||
|
volumes:
|
||||||
|
- ./data:/data:ro
|
||||||
|
- ./backups:/backups
|
||||||
|
environment:
|
||||||
|
- TZ=UTC
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
apk add --no-cache tzdata &&
|
||||||
|
while true; do
|
||||||
|
timestamp=$$(date +%Y%m%d-%H%M%S)
|
||||||
|
echo \"Creating backup at $$timestamp\"
|
||||||
|
cp /data/sqlite.db \"/backups/sqlite-$$timestamp.db\" 2>/dev/null || echo 'No database file found yet'
|
||||||
|
# Keep backups for 7 days
|
||||||
|
find /backups -name 'sqlite-*.db' -mtime +7 -delete 2>/dev/null || true
|
||||||
|
echo \"Backup completed, sleeping for 24 hours\"
|
||||||
|
sleep 86400
|
||||||
|
done"
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- tt-booking
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Starting TT Booking Production..."
|
||||||
|
|
||||||
|
# Create required directories if they don't exist
|
||||||
|
echo "📁 Ensuring directories exist..."
|
||||||
|
mkdir -p /app/data /app/backups /app/logs
|
||||||
|
chmod 755 /app/data /app/backups /app/logs
|
||||||
|
|
||||||
|
# Set defaults for environment variables
|
||||||
|
export DATABASE_URL="${DATABASE_URL:-/app/data/sqlite.db}"
|
||||||
|
export NODE_ENV="${NODE_ENV:-production}"
|
||||||
|
export ADMIN_EMAIL="${ADMIN_EMAIL:-admin@tabletennis.com}"
|
||||||
|
export ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin123}"
|
||||||
|
|
||||||
|
# Validate required environment variables
|
||||||
|
if [ -z "$NEXTAUTH_SECRET" ]; then
|
||||||
|
echo "❌ NEXTAUTH_SECRET is required but not set"
|
||||||
|
echo "💡 Generate one with: openssl rand -base64 32"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$NEXTAUTH_URL" ]; then
|
||||||
|
echo "❌ NEXTAUTH_URL is required but not set"
|
||||||
|
echo "💡 Set to your application URL, e.g., https://your-domain.com"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check database state and determine what actions are needed
|
||||||
|
echo "� Analyzing database state..."
|
||||||
|
DB_CHECK_EXIT=0
|
||||||
|
npx tsx scripts/check-database.ts || DB_CHECK_EXIT=$?
|
||||||
|
|
||||||
|
case $DB_CHECK_EXIT in
|
||||||
|
0)
|
||||||
|
echo "✅ Database is ready - no action needed"
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo "� Database needs migration..."
|
||||||
|
npm run db:push
|
||||||
|
echo "✅ Migration completed"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo "🌱 Database needs seeding..."
|
||||||
|
echo " Admin Email: $ADMIN_EMAIL"
|
||||||
|
echo " Admin Password: [HIDDEN]"
|
||||||
|
npm run db:seed
|
||||||
|
echo "✅ Seeding completed"
|
||||||
|
echo "💡 You can now login with: $ADMIN_EMAIL / $ADMIN_PASSWORD"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
echo "🔄 Database needs migration and seeding..."
|
||||||
|
npm run db:push
|
||||||
|
echo "✅ Migration completed"
|
||||||
|
echo "🌱 Seeding database..."
|
||||||
|
echo " Admin Email: $ADMIN_EMAIL"
|
||||||
|
echo " Admin Password: [HIDDEN]"
|
||||||
|
npm run db:seed
|
||||||
|
echo "✅ Database initialization completed"
|
||||||
|
echo "💡 You can now login with: $ADMIN_EMAIL / $ADMIN_PASSWORD"
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
echo "❌ Database state check failed - see logs above"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "❌ Unexpected database check result: $DB_CHECK_EXIT"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "🌟 Starting server..."
|
||||||
|
|
||||||
|
# Execute the main command
|
||||||
|
exec "$@"
|
||||||
+3
-2
@@ -15,6 +15,7 @@
|
|||||||
"db:reset": "tsx scripts/reset-db.ts",
|
"db:reset": "tsx scripts/reset-db.ts",
|
||||||
"db:reset-confirm": "tsx scripts/reset-db.ts --confirm",
|
"db:reset-confirm": "tsx scripts/reset-db.ts --confirm",
|
||||||
"db:seed": "tsx scripts/setup-database.ts --essential-only",
|
"db:seed": "tsx scripts/setup-database.ts --essential-only",
|
||||||
|
"db:check": "tsx scripts/check-database.ts",
|
||||||
"postinstall": "npm run db:init"
|
"postinstall": "npm run db:init"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"drizzle-kit": "^0.20.6",
|
||||||
"drizzle-orm": "^0.29.1",
|
"drizzle-orm": "^0.29.1",
|
||||||
"drizzle-zod": "^0.5.1",
|
"drizzle-zod": "^0.5.1",
|
||||||
"jose": "^6.1.0",
|
"jose": "^6.1.0",
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"tailwind-merge": "^2.1.0",
|
"tailwind-merge": "^2.1.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"tsx": "^4.20.5",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -66,12 +69,10 @@
|
|||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"drizzle-kit": "^0.20.6",
|
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
"eslint-config-next": "^15.5.3",
|
"eslint-config-next": "^15.5.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.3.0",
|
"tailwindcss": "^3.3.0",
|
||||||
"tsx": "^4.20.5",
|
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||||
|
import * as schema from '../lib/db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
interface InitResult {
|
||||||
|
needsMigration: boolean;
|
||||||
|
needsSeeding: boolean;
|
||||||
|
hasData: boolean;
|
||||||
|
adminExists: boolean;
|
||||||
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkDatabaseState(): Promise<InitResult> {
|
||||||
|
const dbPath = process.env.DATABASE_URL || './data/sqlite.db';
|
||||||
|
const result: InitResult = {
|
||||||
|
needsMigration: false,
|
||||||
|
needsSeeding: false,
|
||||||
|
hasData: false,
|
||||||
|
adminExists: false,
|
||||||
|
summary: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if database file exists
|
||||||
|
if (!existsSync(dbPath)) {
|
||||||
|
result.needsMigration = true;
|
||||||
|
result.needsSeeding = true;
|
||||||
|
result.summary = 'Database file does not exist - full initialization needed';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sqlite: Database.Database | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sqlite = new Database(dbPath);
|
||||||
|
const db = drizzle(sqlite, { schema });
|
||||||
|
|
||||||
|
// Check if core tables exist by trying to query them
|
||||||
|
try {
|
||||||
|
const userCount = db.select().from(schema.users).limit(1).all();
|
||||||
|
const courtCount = db.select().from(schema.courts).limit(1).all();
|
||||||
|
const settingsCount = db.select().from(schema.settings).limit(1).all();
|
||||||
|
|
||||||
|
// Database has tables and some basic structure
|
||||||
|
result.hasData = userCount.length > 0 || courtCount.length > 0 || settingsCount.length > 0;
|
||||||
|
|
||||||
|
// Check for admin users specifically
|
||||||
|
const adminUsers = db
|
||||||
|
.select()
|
||||||
|
.from(schema.users)
|
||||||
|
.where(eq(schema.users.role, 'admin'))
|
||||||
|
.limit(1)
|
||||||
|
.all();
|
||||||
|
|
||||||
|
result.adminExists = adminUsers.length > 0;
|
||||||
|
|
||||||
|
// Determine what's needed
|
||||||
|
if (!result.hasData) {
|
||||||
|
result.needsSeeding = true;
|
||||||
|
result.summary = 'Database exists with empty tables - seeding needed';
|
||||||
|
} else if (!result.adminExists) {
|
||||||
|
result.needsSeeding = true;
|
||||||
|
result.summary = 'Database has data but no admin users found - admin creation needed';
|
||||||
|
} else {
|
||||||
|
result.summary = `Database ready - found ${userCount.length ? 'users' : 'no users'}, ${courtCount.length ? 'courts' : 'no courts'}, admin users present`;
|
||||||
|
}
|
||||||
|
} catch (tableError) {
|
||||||
|
// Tables don't exist or schema is outdated
|
||||||
|
result.needsMigration = true;
|
||||||
|
result.needsSeeding = true;
|
||||||
|
result.summary = 'Database exists but tables missing/outdated - migration and seeding needed';
|
||||||
|
}
|
||||||
|
} catch (dbError) {
|
||||||
|
// Database file exists but is corrupted or inaccessible
|
||||||
|
result.needsMigration = true;
|
||||||
|
result.needsSeeding = true;
|
||||||
|
result.summary = `Database file exists but inaccessible: ${(dbError as Error).message}`;
|
||||||
|
} finally {
|
||||||
|
if (sqlite) {
|
||||||
|
try {
|
||||||
|
sqlite.close();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore close errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log('🔍 Checking database state...');
|
||||||
|
const state = await checkDatabaseState();
|
||||||
|
|
||||||
|
console.log(`📊 ${state.summary}`);
|
||||||
|
console.log(` Migration needed: ${state.needsMigration ? '✅' : '❌'}`);
|
||||||
|
console.log(` Seeding needed: ${state.needsSeeding ? '✅' : '❌'}`);
|
||||||
|
console.log(` Has existing data: ${state.hasData ? '✅' : '❌'}`);
|
||||||
|
console.log(` Admin user exists: ${state.adminExists ? '✅' : '❌'}`);
|
||||||
|
|
||||||
|
// Output structured result for shell consumption
|
||||||
|
process.env.DB_NEEDS_MIGRATION = state.needsMigration.toString();
|
||||||
|
process.env.DB_NEEDS_SEEDING = state.needsSeeding.toString();
|
||||||
|
process.env.DB_HAS_DATA = state.hasData.toString();
|
||||||
|
process.env.DB_ADMIN_EXISTS = state.adminExists.toString();
|
||||||
|
|
||||||
|
// Exit codes for shell scripting
|
||||||
|
// 0 = ready, 1 = needs migration, 2 = needs seeding, 3 = needs both
|
||||||
|
if (state.needsMigration && state.needsSeeding) {
|
||||||
|
process.exit(3);
|
||||||
|
} else if (state.needsMigration) {
|
||||||
|
process.exit(1);
|
||||||
|
} else if (state.needsSeeding) {
|
||||||
|
process.exit(2);
|
||||||
|
} else {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database state check failed:', error);
|
||||||
|
process.exit(4); // Error state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { checkDatabaseState, type InitResult };
|
||||||
Reference in New Issue
Block a user