Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at dev 3.8 kB view raw
1import { config } from 'dotenv'; 2import e from 'express'; 3import helmet from 'helmet'; 4import cors from 'cors'; 5 6import BotClient from './services/Client'; 7import { ALLOWED_ORIGINS, PORT, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX } from './config'; 8import rateLimit from 'express-rate-limit'; 9import authenticateApiKey from './middlewares/verifyApiKey'; 10import status from './routes/status'; 11import authRoutes from './routes/auth'; 12import todosRoutes from './routes/todos'; 13import apiKeysRoutes from './routes/apiKeys'; 14import remindersRoutes from './routes/reminders'; 15import { resetOldStrikes } from './utils/userStrikes'; 16import logger from './utils/logger'; 17 18config(); 19 20process.on('uncaughtException', (err) => { 21 logger.error('Uncaught Exception:', err); 22 process.exit(1); 23}); 24process.on('unhandledRejection', (reason, promise) => { 25 logger.error('🔥 Unhandled Rejection at:', promise); 26 logger.error('📄 Reason:', reason); 27}); 28 29const app = e(); 30const startTime = Date.now(); 31 32app.use(helmet()); 33app.use( 34 cors({ 35 origin: ALLOWED_ORIGINS 36 ? ALLOWED_ORIGINS.split(',') 37 : ['http://localhost:3000', 'http://localhost:8080'], 38 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], 39 allowedHeaders: ['Content-Type', 'X-API-Key', 'Authorization', 'Cache-Control', 'Pragma'], 40 credentials: true, 41 maxAge: 86400, 42 }), 43); 44app.set('trust proxy', 1); 45app.use( 46 rateLimit({ 47 windowMs: RATE_LIMIT_WINDOW_MS, 48 max: RATE_LIMIT_MAX, 49 message: { error: 'Too many requests, please try again later.' }, 50 standardHeaders: true, 51 legacyHeaders: false, 52 }), 53); 54 55app.use(e.json({ limit: '10mb' })); 56app.use(e.urlencoded({ extended: true, limit: '10mb' })); 57 58app.use((req, res, next) => { 59 res.setHeader('X-Content-Type-Options', 'nosniff'); 60 res.setHeader('X-Frame-Options', 'DENY'); 61 if (req.path.startsWith('/api/')) { 62 res.setHeader('Content-Security-Policy', "default-src 'none'"); 63 res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); 64 res.setHeader('Pragma', 'no-cache'); 65 res.setHeader('Expires', '0'); 66 res.setHeader('Surrogate-Control', 'no-store'); 67 } else { 68 res.setHeader( 69 'Content-Security-Policy', 70 "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:", 71 ); 72 } 73 next(); 74}); 75 76const bot = new BotClient(); 77bot.init(); 78 79app.use('/api/auth', authRoutes); 80app.use('/api/todos', todosRoutes); 81app.use('/api/user/api-keys', apiKeysRoutes); 82app.use('/api/reminders', remindersRoutes); 83 84app.use('/api/status', authenticateApiKey, status(bot)); 85 86app.get('/health', (req, res) => { 87 res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() }); 88}); 89 90app.use(e.static('web/dist')); 91 92app.get('*', (req, res) => { 93 if (req.path.startsWith('/api/')) { 94 return res.status(404).json({ error: 'Not found' }); 95 } 96 res.sendFile('index.html', { root: 'web/dist' }); 97}); 98 99setInterval( 100 () => { 101 resetOldStrikes().catch(logger.error); 102 }, 103 60 * 60 * 1000, 104); 105 106const server = app.listen(PORT, async () => { 107 logger.debug('Aethel is live on', `http://localhost:${PORT}`); 108 109 const { sendDeploymentNotification } = await import('./utils/sendDeploymentNotification'); 110 await sendDeploymentNotification(startTime); 111}); 112 113process.on('SIGTERM', () => { 114 logger.info('SIGTERM received. Shutting down gracefully...'); 115 server.close(() => { 116 logger.info('Server closed'); 117 process.exit(0); 118 }); 119}); 120 121process.on('SIGINT', () => { 122 logger.info('SIGINT received. Shutting down gracefully...'); 123 server.close(() => { 124 logger.info('Server closed'); 125 process.exit(0); 126 }); 127});