Spaces:
Running
Running
File size: 9,359 Bytes
e8a6607 fe2dc13 e8a6607 9e90bda e8a6607 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | import express, { Request, Response, NextFunction, Express } from 'express'; //use express module to create a server obj
import IORedis from 'ioredis';
// import { Queue } from 'bullmq';
import { rateLimit, ipKeyGenerator } from 'express-rate-limit';
//For flutter app .dev purpose
import http, { Server as HttpServer } from "http";
import https, { Server as HttpsServer } from 'https';
import cors from 'cors';
import helmet from 'helmet';
import path from 'path';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
import logger from './logger';
import { appRouter } from './api/routes/index'; // import a centralized routes group
import { initializeSocket } from './api/socket/socket';
import { RedisClientInfra } from './redis';
import { QueueInfra } from './queues';
// import { GptChatWorker } from './workers';
import { GptChatQueueService } from './queues/gpt-chat-queue-services';
import {
initPubSubRedis,
closePubSubRedis
} from './redis';
import * as fs from 'fs';
// Load env file based on NODE_ENV
const envFile: string = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development';
dotenv.config({ path: envFile });
const certPath = process.env.SSL_CERT || './ssl/certificate.crt';
const keyPath = process.env.SSL_KEY || './ssl/certificate.key';
const allowedOrigins = [process.env.FRONTEND_URL as string];
const host: string | undefined = process.env.HOST;
const port: number = Number(process.env.PORT || 7860);
const app: Express = express(); // create a server
// Global rate limiting
const globalLimiter = rateLimit({
windowMs: 1 * 1000, // 1 second
max: 10000, // 10,000 requests per second globally
handler: (req: Request, res: Response) => {
res.status(429).json({ error: 'System overloaded' });
}
});
// Express rate limiter configuration
const ipLimiter = rateLimit({
windowMs: 10 * 1000, // 10 Sec window
max: 60, // Maximum 60 requests per 10 Secs per user
keyGenerator: (req: Request) => ipKeyGenerator(req.ip ?? 'unknown-ip'),
message: { message: 'Too many requests from this IP, please try again after some time' },
headers: true,
});
const corsOptions = {
origin: function (origin: string | undefined, callback: Function) {
// Allow requests with no origin (like mobile apps or curl requests)
console.log("CORS callback origin:", origin);
console.log("Allowed:", allowedOrigins);
if (!origin) {
console.log("No origin");
return callback(null, true);
}
if (allowedOrigins.includes(origin)) {
console.log("Origin allowed");
callback(null, origin);
} else {
console.log("Cors blocked");
return callback(new Error("Not allowed by CORS"));
}
},
credentials: true,
exposedHeaders: ['Authorization'],
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Requested-With',
'x-socket-id',
'x-anonuser-id',
// 'Cookie'
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
};
if (process.env.NODE_ENV !== 'production') {
app.use((req, res, next) => {
console.log("Method:", req.method);
console.log("Origin:", req.headers.origin);
next();
});
}
const publicPath = path.join(__dirname, "../public");
console.log("Public path:", publicPath);
console.log("Exists:", fs.existsSync(publicPath));
console.log("Index exists:", fs.existsSync(path.join(publicPath, "index.html")));
app.use(express.static(publicPath));
// Test route
app.get("/health", (req, res) => {
res.status(200).send("Backend is alive");
});
app.get("/*splat", (req, res) => {
res.sendFile(path.join(__dirname, "../public", "index.html"));
});
app.use(cors(corsOptions));
app.options(/.*/, cors(corsOptions));
app.set("trust proxy", 1);
app.use(globalLimiter);
app.use(ipLimiter);
app.use(cookieParser());
app.use(helmet());
app.use(express.json()); //middleware to parse JSON
let server: HttpsServer | HttpServer;
if (process.env.NODE_ENV === 'production') {
// HTTP for production
server = http.createServer(app); //This binding is required to handle some low-level server event handling
} else {
// HTTPS for development
const cert: Buffer = fs.readFileSync(certPath);
const key: Buffer = fs.readFileSync(keyPath);
server = https.createServer({ key, cert }, app); //This binding is required to handle some low-level server event handling
}
let aiQueueRedisInfra: RedisClientInfra;
let aiQueueInfra: QueueInfra;
// let gptChatWorker: GptChatWorker;
let gptChatQueueService: GptChatQueueService;
async function initQueueWorkers() {
// Setup redis connection
aiQueueRedisInfra = new RedisClientInfra('gpt-chat-redis');
aiQueueRedisInfra.init();
aiQueueRedisInfra.testRedisConnection();
// Setup and start queue
aiQueueInfra = new QueueInfra(aiQueueRedisInfra.redis, 'gpt-chat');
aiQueueInfra.init();
aiQueueInfra.testQueueRedisConnection();
// Setup and start worker
// gptChatWorker = new GptChatWorker(aiQueueRedisInfra.redis, 'gpt-chat');
// gptChatWorker.init();
gptChatQueueService = new GptChatQueueService(aiQueueRedisInfra.redis, aiQueueInfra.queue, 'gpt-chat');
};
async function bootStrap() {
try {
logger.info('π Server starting...');
// Redis socket notification
await initPubSubRedis('api-pub-redis', 'api-sub-redis');
await initializeSocket(server); //Initialize socket.io with the server
// Start API Queue and workers
await initQueueWorkers();
if (gptChatQueueService) {
app.use(appRouter(gptChatQueueService));
} else {
logger.error('Some services are still not ready, restart the server');
}
server.listen(port , () => {
logger.info(`Server running on ${host}:${port}`);
});
} catch (err: any) {
logger.error('Server startup failed', err);
process.exit(1);
}
};
bootStrap();
//Confirm that the server is listening after successfull start
server.on('listening', () => {
logger.info('Server successfully started');
logger.info(`Server is listening on localhost:${port}`);
});
//Handle error when server starts
server.on('error', (err: NodeJS.ErrnoException) => {
logger.error({err}, `Server error: , ${err.message}`);
if (err.code === 'EADDRINUSE') { //In case any error happened by the selected port number
logger.warn(`Port ${port} is already in use`);
//////////////////////////////////////
//code to handle to manually select a port and manual restart of server using electron
//////////////////////////////////////
}
});
server.on('close', () => {
logger.info('All API connections closed');
});
const closeServer = () => {
return new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
logger.warn('Force closing server (timeout)');
resolve();
}, 10000);
server.close(() => {
clearTimeout(timeout); // β stop timeout
logger.info('HTTP server closed');
resolve();
});
});
};
//Server and database shut down function
const serverShutdown = async (): Promise<void> => {
try {
logger.info('Server shutting down started...');
await closeServer();
// Stop all Workers, Queueu events, Queueu and Redis for queue
if (aiQueueInfra) {
// await gptChatWorker.closeGptChatWorker(); // First close all workers related to the queue
await gptChatQueueService.closeQueueEvents(); // Then close all queue events related to queue
await aiQueueInfra.closeQueue(); // Then close all queue
await aiQueueInfra.closeQueueRedis(); // At last close the redis connection which makes the queue possible
}
} catch (err: any) {
logger.error({err}, 'β Error stopping schedulers');
}
};
const withTimeout = (promise: Promise<any>, timeoutMs: number, operation: string): Promise<any> => {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms for ${operation}`)), timeoutMs)
)
]);
};
let isGracefullShuttingDown = false;
const gracefulShutdown = async (signal?: string): Promise<void> => {
if (isGracefullShuttingDown) {
logger.warn(`Shutdown already running. Ignoring ${signal}`);
return;
}
isGracefullShuttingDown = true;
logger.info(`Received ${signal}. Starting graceful shutdown...`);
try {
logger.info('π¨ Starting graceful shutdown process...');
logger.info('π Server shutdown called...');
await withTimeout(serverShutdown(), 60000, 'serverShutdown');
logger.info('β
Server shutdown completed');
logger.info('π All cleanup completed successfully');
process.exit(0);
} catch (err: any) {
logger.error({err}, 'β Shutdown error');
process.exit(1);
}
};
process.removeAllListeners('SIGTERM');
process.removeAllListeners('SIGINT');
// Signal listeners
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown); |