import express from 'express'; import cors from 'cors'; import { fileURLToPath } from 'url'; import path from 'path'; import dotenv from 'dotenv'; import swaggerUi from 'swagger-ui-express'; import projectsRouter from './src/routes/projects.js'; import ticketsRouter from './src/routes/tickets.js'; import flowRouter from './src/routes/flow.js'; import chefsysRouter from './src/routes/chefsys.js'; import { openapiSpec } from './src/openapi.js'; dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.PORT || 3000; // Derive the Supabase storage origin so images can be whitelisted explicitly // (instead of using a wildcard) in the Content-Security-Policy. let supabaseOrigin = ''; try { if (process.env.SUPABASE_URL) supabaseOrigin = new URL(process.env.SUPABASE_URL).origin; } catch { supabaseOrigin = ''; } const baseCsp = [ "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src 'self' https://fonts.gstatic.com", `img-src 'self' data: blob:${supabaseOrigin ? ' ' + supabaseOrigin : ''}`, `connect-src 'self'${supabaseOrigin ? ' ' + supabaseOrigin : ''}`, "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'" ].join('; '); // Swagger UI ships inline scripts/styles, so it needs a slightly relaxed policy. const swaggerCsp = [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data:", "object-src 'none'", "base-uri 'self'", "frame-ancestors 'none'" ].join('; '); app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', req.path.startsWith('/api-docs') ? swaggerCsp : baseCsp ); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Referrer-Policy', 'no-referrer'); next(); }); app.use(cors()); app.use(express.json({ limit: '5mb' })); // API routes app.use('/api/projects', projectsRouter); app.use('/api/tickets', ticketsRouter); app.use('/flow/tickets', flowRouter); app.use('/chefsys', chefsysRouter); app.get('/api/health', (req, res) => res.json({ ok: true })); // API documentation (Swagger UI) + raw OpenAPI spec app.get('/api-docs.json', (req, res) => res.json(openapiSpec)); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openapiSpec, { customSiteTitle: 'Taboard API Docs' })); // Static frontend app.use(express.static(path.join(__dirname, 'public'))); app.listen(PORT, () => { console.log(`Taboard running at http://localhost:${PORT}`); });