221 lines
18 KiB
JavaScript
221 lines
18 KiB
JavaScript
import { serve } from '@hono/node-server';
|
|
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
import { swaggerUI } from '@hono/swagger-ui';
|
|
import { Scalar } from '@scalar/hono-api-reference';
|
|
import { cors } from 'hono/cors';
|
|
import dotenv from 'dotenv';
|
|
import path from 'path';
|
|
// Load environment variables based on NODE_ENV
|
|
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env';
|
|
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
|
|
import { logger } from './commons/logger.js';
|
|
import { WebSocketManager } from './commons/websocket.js';
|
|
import { optionalAuthMiddleware, adminMiddleware } from './middleware/auth.js';
|
|
import { analyticsMiddleware } from './middleware/analytics.js';
|
|
import { compress } from 'hono/compress';
|
|
import { secureHeaders } from 'hono/secure-headers';
|
|
// Import endpoints
|
|
import { registerProductRoutes, startProducts } from './products/registry.js';
|
|
const app = new OpenAPIHono();
|
|
// Middleware
|
|
app.use('/*', cors({
|
|
origin: '*',
|
|
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
|
allowHeaders: ['Content-Type', 'Authorization', 'x-stainless-os', 'x-stainless-lang', 'x-stainless-arch', 'x-stainless-package-version', 'x-stainless-runtime', 'x-stainless-runtime-version', 'x-stainless-helper-method', 'x-stainless-retry-count'],
|
|
exposeHeaders: ['Content-Length', 'X-Cache'],
|
|
maxAge: 600,
|
|
credentials: true,
|
|
}));
|
|
// Apply blocklist to all API routes (before rate limiting)
|
|
//app.use('/api/*', blocklistMiddleware)
|
|
// Apply auto-ban middleware (checks ban.json for auto-banned IPs/users)
|
|
// app.use('/api/*', autoBanMiddleware)
|
|
// Apply Analytics (tracks requests to file)
|
|
app.use('*', analyticsMiddleware);
|
|
// Apply Authentication & Authorization
|
|
app.use('/api/*', optionalAuthMiddleware);
|
|
app.use('/api/*', adminMiddleware);
|
|
// app.use('/api/*', apiRateLimiter)
|
|
// Apply compression to all API routes
|
|
// Apply compression to all routes (API + Static Assets)
|
|
app.use('*', compress());
|
|
app.use(secureHeaders({
|
|
crossOriginResourcePolicy: false,
|
|
crossOriginOpenerPolicy: false,
|
|
crossOriginEmbedderPolicy: false,
|
|
xFrameOptions: false,
|
|
contentSecurityPolicy: {
|
|
frameAncestors: ["'self'", "*"]
|
|
}
|
|
}));
|
|
// Register API routes
|
|
import { createLogRoutes, createLogHandlers } from './commons/log-routes-factory.js';
|
|
import { registerAssetRoutes } from './serve-assets.js';
|
|
// System Logs
|
|
const { getRoute: sysGetLogRoute, streamRoute: sysStreamLogRoute } = createLogRoutes('System', '/api/logs/system');
|
|
const { getHandler: sysGetLogHandler, streamHandler: sysStreamLogHandler } = createLogHandlers(path.join(process.cwd(), 'app.log'));
|
|
app.openapi(sysGetLogRoute, sysGetLogHandler);
|
|
app.openapi(sysStreamLogRoute, sysStreamLogHandler);
|
|
// Register Product Routes
|
|
await registerProductRoutes(app);
|
|
// Initialize Products
|
|
// Products initialized after PgBoss check below
|
|
// API Documentation (Development Only)
|
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
if (isDevelopment) {
|
|
logger.info('Registering API documentation endpoints (development mode)');
|
|
// Swagger UI
|
|
app.doc31('/doc', {
|
|
openapi: '3.1.0',
|
|
info: {
|
|
version: '1.0.0',
|
|
title: 'Images API',
|
|
},
|
|
components: {
|
|
securitySchemes: {
|
|
bearerAuth: {
|
|
type: 'http',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'JWT',
|
|
},
|
|
},
|
|
},
|
|
security: [
|
|
{
|
|
bearerAuth: [],
|
|
},
|
|
],
|
|
});
|
|
// Swagger UI
|
|
app.get('/ui', swaggerUI({ url: '/doc' }));
|
|
// Scalar API Reference
|
|
app.get('/reference', Scalar({
|
|
spec: {
|
|
url: '/doc',
|
|
},
|
|
authentication: {
|
|
preferredSecurityScheme: 'bearerAuth',
|
|
httpBearer: {
|
|
token: process.env.SCALAR_AUTH_TOKEN || '',
|
|
},
|
|
},
|
|
}));
|
|
// Alternative: API Reference at /api/reference
|
|
app.get('/api/reference', Scalar({
|
|
spec: {
|
|
url: '/doc',
|
|
},
|
|
authentication: {
|
|
preferredSecurityScheme: 'bearerAuth',
|
|
httpBearer: {
|
|
token: process.env.SCALAR_AUTH_TOKEN || '',
|
|
}
|
|
},
|
|
}));
|
|
}
|
|
else {
|
|
logger.info('API documentation endpoints disabled (production mode)');
|
|
}
|
|
import { postBossJobRoute, postBossJobHandler, getBossJobRoute, getBossJobHandler, cancelBossJobRoute, cancelBossJobHandler, resumeBossJobRoute, resumeBossJobHandler, completeBossJobRoute, completeBossJobHandler, failBossJobRoute, failBossJobHandler } from './endpoints/boss.js';
|
|
import { startBoss } from './jobs/boss/client.js';
|
|
import { registerMockWorkers } from './jobs/boss/workers.js';
|
|
// Register PgBoss routes
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(postBossJobRoute, postBossJobHandler);
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(getBossJobRoute, getBossJobHandler);
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(cancelBossJobRoute, cancelBossJobHandler);
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(resumeBossJobRoute, resumeBossJobHandler);
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(completeBossJobRoute, completeBossJobHandler);
|
|
// @ts-ignore - Route type mismatch
|
|
app.openapi(failBossJobRoute, failBossJobHandler);
|
|
// Register Streaming Route
|
|
import { getStreamRoute, streamHandler } from './endpoints/stream.js';
|
|
app.openapi(getStreamRoute, streamHandler);
|
|
// Register Admin Routes
|
|
import { registerAdminRoutes } from './endpoints/admin.js';
|
|
import { AdminEndpointRegistry } from './commons/registry.js';
|
|
// Register restart endpoint as admin-only
|
|
AdminEndpointRegistry.register('/api/admin/system/restart', 'POST');
|
|
// Register ban management endpoints as admin-only
|
|
AdminEndpointRegistry.register('/api/admin/bans', 'GET');
|
|
AdminEndpointRegistry.register('/api/admin/bans/unban-ip', 'POST');
|
|
AdminEndpointRegistry.register('/api/admin/bans/unban-user', 'POST');
|
|
AdminEndpointRegistry.register('/api/admin/bans/violations', 'GET');
|
|
AdminEndpointRegistry.register('/api/analytics', 'GET');
|
|
AdminEndpointRegistry.register('/api/analytics/stream', 'GET');
|
|
AdminEndpointRegistry.register('/api/analytics', 'DELETE');
|
|
registerAdminRoutes(app);
|
|
// Register Asset Routes (Static files, SW, SPA fallback)
|
|
// IMPORTANT: This MUST be registered AFTER all API routes to prevent the catch-all from intercepting API calls
|
|
registerAssetRoutes(app);
|
|
// Initialize PgBoss
|
|
// Initialize PgBoss and Products
|
|
try {
|
|
const boss = await startBoss();
|
|
if (boss) {
|
|
registerMockWorkers();
|
|
try {
|
|
await startProducts(boss);
|
|
}
|
|
catch (err) {
|
|
logger.error({ err }, 'Failed to init products with Boss');
|
|
}
|
|
}
|
|
else {
|
|
// Fallback: Start products without Boss
|
|
logger.info('Starting products without PgBoss');
|
|
await startProducts();
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.error({ err }, 'Failed to init PgBoss');
|
|
// Fallback: Start products without Boss on error
|
|
logger.info('Starting products without PgBoss (after error)');
|
|
await startProducts();
|
|
}
|
|
const port = parseInt(process.env.PORT || '3333', 10);
|
|
logger.info(`Server is running on port ${port}`);
|
|
// Only start the server if not in test mode
|
|
if (process.env.NODE_ENV !== 'test' && !process.env.VITEST) {
|
|
const server = serve({
|
|
fetch: app.fetch,
|
|
port
|
|
});
|
|
// Initialize WebSocket Server
|
|
if (process.env.ENABLE_WEBSOCKETS === 'true') {
|
|
WebSocketManager.getInstance().init(server);
|
|
}
|
|
let isShuttingDown = false;
|
|
const gracefulShutdown = (signal) => {
|
|
if (isShuttingDown) {
|
|
logger.warn('Already shutting down...');
|
|
return;
|
|
}
|
|
isShuttingDown = true;
|
|
// Force exit after a timeout
|
|
const timeout = setTimeout(() => {
|
|
logger.warn('Shutdown timed out. Forcing exit.');
|
|
process.exit(1);
|
|
}, 5000);
|
|
server.close(async (err) => {
|
|
if (err) {
|
|
logger.error({ err }, 'Error closing HTTP server');
|
|
}
|
|
else {
|
|
console.log('HTTP server closed.');
|
|
}
|
|
clearTimeout(timeout);
|
|
console.log('Gracefully shut down.');
|
|
process.exit(err ? 1 : 0);
|
|
});
|
|
};
|
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
process.on('SIGBREAK', () => gracefulShutdown('SIGBREAK')); // For Windows
|
|
}
|
|
export { app };
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ3pDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUMvQyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sa0JBQWtCLENBQUE7QUFDNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFBO0FBQ25ELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDaEMsT0FBTyxNQUFNLE1BQU0sUUFBUSxDQUFBO0FBQzNCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUV2QiwrQ0FBK0M7QUFDL0MsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEtBQUssWUFBWSxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFBO0FBQ2xGLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFBO0FBRTdELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQTtBQUM1QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUsxRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsZUFBZSxFQUFFLE1BQU0sc0JBQXNCLENBQUE7QUFDOUUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sMkJBQTJCLENBQUE7QUFHL0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQTtBQUN4QyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFFbkQsbUJBQW1CO0FBRW5CLE9BQU8sRUFB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
|