import Fastify from 'fastify'; import cors from '@fastify/cors'; import jwt from '@fastify/jwt'; import multipart from '@fastify/multipart'; import cookie from '@fastify/cookie'; import { authenticate } from './middleware/auth.middleware.js'; import { env, validateEnv } from './config/env.js'; import { db, initDatabase } from './config/database.js'; import fastifyStatic from '@fastify/static'; import path from 'path'; // Import routes import authRoute from './routes/auth.js'; import eventsRoute from './routes/events.js'; import galleryRoute from './routes/gallery.js'; import contentRoute from './routes/content.js'; import settingsRoute from './routes/settings.js'; import publishRoute from './routes/publish.js'; import bannersRoute from './routes/banners.js'; // Validate environment variables try { validateEnv(); } catch (error) { console.error('Environment validation failed:', error); process.exit(1); } const fastify = Fastify({ logger: { level: env.NODE_ENV === 'production' ? 'info' : 'debug', transport: env.NODE_ENV === 'development' ? { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', }, } : undefined, }, }); // Register plugins // Support multiple origins for CORS const allowedOrigins = env.CORS_ORIGIN.split(',').map(o => o.trim()); fastify.register(cors, { origin: (origin, cb) => { // Allow requests with no origin (like mobile apps or curl) if (!origin) { return cb(null, true); } // Check if origin is in allowed list const isAllowed = allowedOrigins.includes(origin); if (isAllowed) { return cb(null, true); } else { return cb(null, false); } }, credentials: true, }); fastify.register(cookie); fastify.register(jwt, { secret: env.JWT_SECRET, cookie: { cookieName: 'token', signed: false, }, }); fastify.register(multipart, { limits: { fileSize: env.MAX_FILE_SIZE, }, }); // Serve static files (uploaded images, etc.) from persistent volume const dataDir = env.GIT_WORKSPACE_DIR || path.join(process.cwd(), 'data'); fastify.register(fastifyStatic, { root: dataDir, prefix: '/static/', decorateReply: false }); // Decorate fastify with authenticate method fastify.decorate('authenticate', authenticate); // Register routes fastify.register(authRoute, { prefix: '/api' }); fastify.register(eventsRoute, { prefix: '/api' }); fastify.register(galleryRoute, { prefix: '/api' }); fastify.register(contentRoute, { prefix: '/api' }); fastify.register(settingsRoute, { prefix: '/api' }); fastify.register(publishRoute, { prefix: '/api' }); fastify.register(bannersRoute, { prefix: '/api' }); // Health check fastify.get('/health', async () => { return { status: 'ok', timestamp: new Date().toISOString(), environment: env.NODE_ENV, }; }); // Root endpoint fastify.get('/', async () => { return { name: 'Gallus Pub CMS Backend', version: '1.0.0', status: 'running', }; }); // Error handler fastify.setErrorHandler((error, request, reply) => { fastify.log.error(error); reply.status(error.statusCode || 500).send({ error: error.message || 'Internal Server Error', statusCode: error.statusCode || 500, }); }); // Start server const start = async () => { try { // Initialize database before starting server initDatabase(); await fastify.listen({ port: env.PORT, host: '0.0.0.0' }); console.log(`🚀 Server listening on port ${env.PORT}`); console.log(`📝 Environment: ${env.NODE_ENV}`); console.log(`🔐 CORS Origin: ${env.CORS_ORIGIN}`); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();