95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import express from 'express';
|
|
import { createServer } from 'http';
|
|
import { toNodeHandler } from 'better-auth/node';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import swaggerUi from 'swagger-ui-express';
|
|
|
|
import { auth } from './auth';
|
|
import { buildOpenApiDocument } from './docs/openapi';
|
|
import videosRoutes from './routes/videos';
|
|
import adminRoutes from './routes/admin';
|
|
import devicesRoutes from './routes/devices';
|
|
import deviceLinksRoutes from './routes/device-links';
|
|
import commandsRoutes from './routes/commands';
|
|
import eventsRoutes from './routes/events';
|
|
import streamsRoutes from './routes/streams';
|
|
import recordingsRoutes from './routes/recordings';
|
|
import pushNotificationsRoutes from './routes/push-notifications';
|
|
import auditRoutes from './routes/audit';
|
|
import opsRoutes from './routes/ops';
|
|
import { rateLimit } from './middleware/security';
|
|
import { requestContext } from './middleware/observability';
|
|
import { setupRealtimeGateway } from './realtime/gateway';
|
|
import { ensureMinioBucket } from './utils/minio';
|
|
import { startRecordingsWorker } from './workers/recordings';
|
|
import { startPushWorker } from './services/push';
|
|
|
|
const app = express();
|
|
const openApiDocument = buildOpenApiDocument();
|
|
const trustedOrigins = process.env.BETTER_AUTH_TRUSTED_ORIGINS
|
|
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(',').map((origin) => origin.trim()).filter(Boolean)
|
|
: [];
|
|
|
|
app.get('/', (_req, res) => {
|
|
res.send('API is running');
|
|
});
|
|
|
|
app.get('/openapi.json', (_req, res) => {
|
|
res.json(openApiDocument);
|
|
});
|
|
|
|
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));
|
|
|
|
app.all('/api/auth/*splat', toNodeHandler(auth));
|
|
|
|
app.use(helmet());
|
|
app.use(
|
|
cors({
|
|
origin: trustedOrigins.length > 0 ? trustedOrigins : true,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
app.use(rateLimit({ keyPrefix: 'global', windowMs: 60_000, max: 400 }));
|
|
app.use(requestContext);
|
|
app.use(express.json());
|
|
app.use('/sim', express.static('public'));
|
|
app.use('/videos', videosRoutes);
|
|
app.use('/admin', adminRoutes);
|
|
app.use('/devices', rateLimit({ keyPrefix: 'devices', windowMs: 60_000, max: 120 }), devicesRoutes);
|
|
app.use('/device-links', deviceLinksRoutes);
|
|
app.use('/commands', rateLimit({ keyPrefix: 'commands', windowMs: 60_000, max: 120 }), commandsRoutes);
|
|
app.use('/events', rateLimit({ keyPrefix: 'events', windowMs: 60_000, max: 120 }), eventsRoutes);
|
|
app.use('/streams', rateLimit({ keyPrefix: 'streams', windowMs: 60_000, max: 120 }), streamsRoutes);
|
|
app.use('/recordings', rateLimit({ keyPrefix: 'recordings', windowMs: 60_000, max: 120 }), recordingsRoutes);
|
|
app.use('/push-notifications', pushNotificationsRoutes);
|
|
app.use('/audit', auditRoutes);
|
|
app.use('/ops', opsRoutes);
|
|
|
|
app.use((err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
console.error(err);
|
|
res.status(500).json({ message: 'Internal server error' });
|
|
});
|
|
|
|
const port = Number(process.env.PORT ?? 3000);
|
|
const server = createServer(app);
|
|
|
|
const start = async () => {
|
|
try {
|
|
await ensureMinioBucket();
|
|
} catch (error) {
|
|
console.error('MinIO initialization failed', error);
|
|
process.exit(1);
|
|
}
|
|
|
|
setupRealtimeGateway(server);
|
|
startRecordingsWorker();
|
|
startPushWorker();
|
|
|
|
server.listen(port, () => {
|
|
console.log(`Server is running on port ${port}`);
|
|
});
|
|
};
|
|
|
|
start();
|