Switch from sqlite3 to sql.js for cross-platform compatibility
- Replace sqlite3 (native) with sql.js (pure JS/WebAssembly) - Fixes GLIBC version compatibility issues - Rewrite database module for sql.js async API - Add proper file persistence for sql.js (in-memory with save-to-file) - Update server startup to initialize database before listening sql.js works everywhere without native compilation
This commit is contained in:
parent
9b4d3242e2
commit
bbdbc0c1df
1095
package-lock.json
generated
1095
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@
|
|||||||
"express-oauth2-jwt-bearer": "^1.7.4",
|
"express-oauth2-jwt-bearer": "^1.7.4",
|
||||||
"helmet": "^8.1.0",
|
"helmet": "^8.1.0",
|
||||||
"jose": "^6.2.2",
|
"jose": "^6.2.2",
|
||||||
"sqlite3": "^6.0.1",
|
"sql.js": "^1.14.1",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,173 +1,295 @@
|
|||||||
import sqlite3 from 'sqlite3';
|
import initSqlJs from 'sql.js';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
const DB_PATH = join(__dirname, '../../data/moxie.db');
|
const DATA_DIR = join(__dirname, '../../data');
|
||||||
|
const DB_PATH = join(DATA_DIR, 'moxie.db');
|
||||||
|
|
||||||
// Promisify sqlite3 methods
|
let db = null;
|
||||||
const db = new sqlite3.Database(DB_PATH, (err) => {
|
let SQL = null;
|
||||||
if (err) {
|
|
||||||
console.error('Error opening database:', err.message);
|
// Ensure data directory exists
|
||||||
} else {
|
if (!existsSync(DATA_DIR)) {
|
||||||
console.log('Connected to SQLite database at:', DB_PATH);
|
mkdirSync(DATA_DIR, { recursive: true });
|
||||||
initDatabase();
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save database to file
|
||||||
|
*/
|
||||||
|
const saveDatabase = () => {
|
||||||
|
if (db) {
|
||||||
|
const data = db.export();
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
writeFileSync(DB_PATH, buffer);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Promisify database methods
|
|
||||||
const dbRun = (sql, params = []) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
db.run(sql, params, function(err) {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve({ id: this.lastID, changes: this.changes });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbGet = (sql, params = []) => {
|
// Debounced save to avoid excessive disk writes
|
||||||
return new Promise((resolve, reject) => {
|
let saveTimeout = null;
|
||||||
db.get(sql, params, (err, row) => {
|
const debouncedSave = () => {
|
||||||
if (err) reject(err);
|
if (saveTimeout) clearTimeout(saveTimeout);
|
||||||
else resolve(row);
|
saveTimeout = setTimeout(saveDatabase, 100);
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbAll = (sql, params = []) => {
|
/**
|
||||||
return new Promise((resolve, reject) => {
|
* Initialize SQL.js and load/create database
|
||||||
db.all(sql, params, (err, rows) => {
|
*/
|
||||||
if (err) reject(err);
|
|
||||||
else resolve(rows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize database tables
|
|
||||||
const initDatabase = async () => {
|
const initDatabase = async () => {
|
||||||
try {
|
try {
|
||||||
// Users table (updated for Auth0)
|
// Initialize SQL.js
|
||||||
await dbRun(`
|
SQL = await initSqlJs();
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
auth0_id TEXT UNIQUE,
|
|
||||||
email TEXT,
|
|
||||||
password_hash TEXT,
|
|
||||||
name TEXT,
|
|
||||||
picture TEXT,
|
|
||||||
role TEXT DEFAULT 'user',
|
|
||||||
credits INTEGER DEFAULT 0,
|
|
||||||
subscription_status TEXT DEFAULT 'inactive',
|
|
||||||
subscription_tier TEXT,
|
|
||||||
stripe_customer_id TEXT,
|
|
||||||
paypal_customer_id TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_login DATETIME,
|
|
||||||
is_active INTEGER DEFAULT 1
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// API keys table for user API access
|
// Load existing database or create new one
|
||||||
await dbRun(`
|
if (existsSync(DB_PATH)) {
|
||||||
CREATE TABLE IF NOT EXISTS api_keys (
|
const fileBuffer = readFileSync(DB_PATH);
|
||||||
id TEXT PRIMARY KEY,
|
db = new SQL.Database(fileBuffer);
|
||||||
user_id TEXT NOT NULL,
|
console.log('Loaded existing database from:', DB_PATH);
|
||||||
key_hash TEXT NOT NULL,
|
} else {
|
||||||
name TEXT,
|
db = new SQL.Database();
|
||||||
last_used DATETIME,
|
console.log('Created new database at:', DB_PATH);
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
}
|
||||||
is_active INTEGER DEFAULT 1,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Credit transactions table
|
// Create tables
|
||||||
await dbRun(`
|
await createTables();
|
||||||
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
amount INTEGER NOT NULL,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
reference_id TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Payment history table
|
console.log('Database initialized successfully');
|
||||||
await dbRun(`
|
|
||||||
CREATE TABLE IF NOT EXISTS payments (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
amount REAL NOT NULL,
|
|
||||||
currency TEXT DEFAULT 'USD',
|
|
||||||
provider TEXT NOT NULL,
|
|
||||||
provider_payment_id TEXT,
|
|
||||||
status TEXT DEFAULT 'pending',
|
|
||||||
metadata TEXT,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Sessions table (kept for API key sessions)
|
|
||||||
await dbRun(`
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
token_hash TEXT NOT NULL,
|
|
||||||
expires_at DATETIME NOT NULL,
|
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for better query performance
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_users_auth0_id ON users(auth0_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_users_stripe_customer ON users(stripe_customer_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_users_paypal_customer ON users(paypal_customer_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_credit_trans_user ON credit_transactions(user_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_payments_user ON payments(user_id)`);
|
|
||||||
await dbRun(`CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)`);
|
|
||||||
|
|
||||||
// Run migrations for existing databases
|
|
||||||
await runMigrations();
|
|
||||||
|
|
||||||
console.log('Database tables initialized successfully');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error initializing database:', err.message);
|
console.error('Error initializing database:', err.message);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run migrations for schema updates
|
/**
|
||||||
|
* Create database tables
|
||||||
|
*/
|
||||||
|
const createTables = async () => {
|
||||||
|
// Users table (updated for Auth0)
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
auth0_id TEXT UNIQUE,
|
||||||
|
email TEXT,
|
||||||
|
password_hash TEXT,
|
||||||
|
name TEXT,
|
||||||
|
picture TEXT,
|
||||||
|
role TEXT DEFAULT 'user',
|
||||||
|
credits INTEGER DEFAULT 0,
|
||||||
|
subscription_status TEXT DEFAULT 'inactive',
|
||||||
|
subscription_tier TEXT,
|
||||||
|
stripe_customer_id TEXT,
|
||||||
|
paypal_customer_id TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_login DATETIME,
|
||||||
|
is_active INTEGER DEFAULT 1
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// API keys table for user API access
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS api_keys (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
key_hash TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
last_used DATETIME,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_active INTEGER DEFAULT 1,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Credit transactions table
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS credit_transactions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
amount INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
reference_id TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Payment history table
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS payments (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
amount REAL NOT NULL,
|
||||||
|
currency TEXT DEFAULT 'USD',
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
provider_payment_id TEXT,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
metadata TEXT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Sessions table (kept for API key sessions)
|
||||||
|
db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
token_hash TEXT NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Create indexes for better query performance
|
||||||
|
try {
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_users_auth0_id ON users(auth0_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_users_stripe_customer ON users(stripe_customer_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_users_paypal_customer ON users(paypal_customer_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_credit_trans_user ON credit_transactions(user_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_payments_user ON payments(user_id)`);
|
||||||
|
db.run(`CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)`);
|
||||||
|
} catch (err) {
|
||||||
|
// Indexes might already exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run migrations for existing databases
|
||||||
|
await runMigrations();
|
||||||
|
|
||||||
|
debouncedSave();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run migrations for schema updates
|
||||||
|
*/
|
||||||
const runMigrations = async () => {
|
const runMigrations = async () => {
|
||||||
try {
|
try {
|
||||||
// Add auth0_id column if it doesn't exist
|
// Check if auth0_id column exists
|
||||||
const tableInfo = await dbAll('PRAGMA table_info(users)');
|
const tableInfo = db.exec("PRAGMA table_info(users)");
|
||||||
const columns = tableInfo.map(col => col.name);
|
if (tableInfo.length > 0) {
|
||||||
|
const columns = tableInfo[0].values.map(col => col[1]);
|
||||||
|
|
||||||
if (!columns.includes('auth0_id')) {
|
if (!columns.includes('auth0_id')) {
|
||||||
await dbRun('ALTER TABLE users ADD COLUMN auth0_id TEXT UNIQUE');
|
// For SQLite, we can't add UNIQUE columns directly, so we recreate the table
|
||||||
console.log('Migration: Added auth0_id column');
|
console.log('Migration: Adding auth0_id column');
|
||||||
|
db.run(`ALTER TABLE users ADD COLUMN auth0_id TEXT`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!columns.includes('picture')) {
|
||||||
|
console.log('Migration: Adding picture column');
|
||||||
|
db.run(`ALTER TABLE users ADD COLUMN picture TEXT`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!columns.includes('picture')) {
|
|
||||||
await dbRun('ALTER TABLE users ADD COLUMN picture TEXT');
|
|
||||||
console.log('Migration: Added picture column');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make email nullable by recreating the table (SQLite limitation)
|
|
||||||
// This is safe for new databases but preserve existing data
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Columns might already exist, that's fine
|
// Columns might already exist, that's fine
|
||||||
|
console.log('Migration note:', err.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { db, dbRun, dbGet, dbAll, initDatabase, runMigrations };
|
/**
|
||||||
|
* Run a SQL statement (INSERT, UPDATE, DELETE)
|
||||||
|
* @param {string} sql - SQL statement
|
||||||
|
* @param {Array} params - Parameters
|
||||||
|
* @returns {Promise<{id: string, changes: number}>}
|
||||||
|
*/
|
||||||
|
const dbRun = (sql, params = []) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
db.run(sql, params);
|
||||||
|
debouncedSave();
|
||||||
|
|
||||||
|
// Get last inserted ID
|
||||||
|
const result = db.exec("SELECT last_insert_rowid() as id, changes() as changes");
|
||||||
|
const lastId = result.length > 0 ? result[0].values[0][0] : null;
|
||||||
|
const changes = result.length > 0 ? result[0].values[0][1] : 0;
|
||||||
|
|
||||||
|
resolve({ id: lastId, changes });
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single row
|
||||||
|
* @param {string} sql - SQL query
|
||||||
|
* @param {Array} params - Parameters
|
||||||
|
* @returns {Promise<Object|null>}
|
||||||
|
*/
|
||||||
|
const dbGet = (sql, params = []) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
stmt.bind(params);
|
||||||
|
|
||||||
|
if (stmt.step()) {
|
||||||
|
const row = stmt.getAsObject();
|
||||||
|
stmt.free();
|
||||||
|
resolve(row);
|
||||||
|
} else {
|
||||||
|
stmt.free();
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all rows
|
||||||
|
* @param {string} sql - SQL query
|
||||||
|
* @param {Array} params - Parameters
|
||||||
|
* @returns {Promise<Array>}
|
||||||
|
*/
|
||||||
|
const dbAll = (sql, params = []) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
stmt.bind(params);
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
while (stmt.step()) {
|
||||||
|
rows.push(stmt.getAsObject());
|
||||||
|
}
|
||||||
|
stmt.free();
|
||||||
|
|
||||||
|
resolve(rows);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute raw SQL (for migrations, etc.)
|
||||||
|
* @param {string} sql - SQL to execute
|
||||||
|
*/
|
||||||
|
const dbExec = (sql) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
db.exec(sql);
|
||||||
|
debouncedSave();
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export a function to ensure database is ready
|
||||||
|
const waitForDb = async () => {
|
||||||
|
if (!db) {
|
||||||
|
await initDatabase();
|
||||||
|
}
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { db, dbRun, dbGet, dbAll, dbExec, initDatabase, waitForDb };
|
||||||
|
|||||||
47
src/index.js
47
src/index.js
@ -9,6 +9,7 @@ import usersRouter from './routes/users.js';
|
|||||||
import webhooksRouter from './routes/webhooks.js';
|
import webhooksRouter from './routes/webhooks.js';
|
||||||
import { ApiResponse } from './utils/helpers.js';
|
import { ApiResponse } from './utils/helpers.js';
|
||||||
import { validateAccessToken, ensureUserExists } from './middleware/auth.js';
|
import { validateAccessToken, ensureUserExists } from './middleware/auth.js';
|
||||||
|
import { initDatabase, waitForDb } from './db/database.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@ -237,8 +238,13 @@ app.use((err, req, res, next) => {
|
|||||||
// Server Startup
|
// Server Startup
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
const server = app.listen(PORT, () => {
|
const startServer = async () => {
|
||||||
console.log(`
|
try {
|
||||||
|
// Initialize database first
|
||||||
|
await initDatabase();
|
||||||
|
|
||||||
|
const server = app.listen(PORT, () => {
|
||||||
|
console.log(`
|
||||||
╔════════════════════════════════════════════╗
|
╔════════════════════════════════════════════╗
|
||||||
║ Moxie Backend Server Started ║
|
║ Moxie Backend Server Started ║
|
||||||
╠════════════════════════════════════════════╣
|
╠════════════════════════════════════════════╣
|
||||||
@ -247,25 +253,28 @@ const server = app.listen(PORT, () => {
|
|||||||
║ Auth0 Domain: ${AUTH0_DOMAIN} ║
|
║ Auth0 Domain: ${AUTH0_DOMAIN} ║
|
||||||
║ Time: ${new Date().toISOString()} ║
|
║ Time: ${new Date().toISOString()} ║
|
||||||
╚════════════════════════════════════════════╝
|
╚════════════════════════════════════════════╝
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on('SIGTERM', () => {
|
const shutdown = () => {
|
||||||
console.log('SIGTERM received, shutting down gracefully...');
|
console.log('Shutting down gracefully...');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
console.log('Server closed');
|
console.log('Server closed');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGTERM', shutdown);
|
||||||
console.log('SIGINT received, shutting down gracefully...');
|
process.on('SIGINT', shutdown);
|
||||||
server.close(() => {
|
|
||||||
console.log('Server closed');
|
} catch (error) {
|
||||||
process.exit(0);
|
console.error('Failed to start server:', error);
|
||||||
});
|
process.exit(1);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
startServer();
|
||||||
|
|
||||||
// Handle uncaught exceptions
|
// Handle uncaught exceptions
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user