/home/techb158/trello.abdallabala.com/src
NameSizeModeActions
riskEngine.js57560666editdlrm
server.js50850666editdlrm
store.js11730666editdlrm
Edit: /home/techb158/trello.abdallabala.com/src/server.js (5085B)
import cors from 'cors'; import dotenv from 'dotenv'; import express from 'express'; import helmet from 'helmet'; import morgan from 'morgan'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { getReferenceTaxonomy, scoreRiskAssessment } from './riskEngine.js'; import { listAssessmentsByBoard, listAssessmentsByCard, saveAssessment, upsertBoard, upsertCard } from './store.js'; dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const port = Number(process.env.PORT || 3000); const publicDir = path.join(__dirname, '..', 'public'); const allowedOrigins = (process.env.ALLOWED_ORIGINS || 'http://localhost:3000,https://trello.com,https://*.trello.com') .split(',') .map((value) => value.trim()) .filter(Boolean); function originAllowed(origin) { if (!origin) return true; return allowedOrigins.some((allowed) => { if (allowed.includes('*')) { const pattern = new RegExp(`^${allowed.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace('\\*', '.*')}$`); return pattern.test(origin); } return allowed === origin; }); } app.disable('x-powered-by'); /* Trello Power-Ups run inside Trello iframes. These security headers must be configured before static files are served. */ app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, crossOriginOpenerPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false, frameguard: false })); app.use((_req, res, next) => { res.removeHeader('X-Frame-Options'); res.removeHeader('Cross-Origin-Opener-Policy'); res.removeHeader('Cross-Origin-Resource-Policy'); /* This allows Trello to embed the Power-Up connector page. Do not set X-Frame-Options for this app. */ res.setHeader('Content-Security-Policy', "frame-ancestors https://trello.com https://*.trello.com;"); next(); }); app.use(cors({ origin: (origin, cb) => cb(originAllowed(origin) ? null : new Error('Origin not allowed'), true) })); app.use(express.json({ limit: '1mb' })); app.use(morgan('tiny')); app.get('/config.js', (_req, res) => { const config = { COSMIC_TRELLO_API_KEY: process.env.TRELLO_API_KEY || 'replace_with_power_up_api_key', COSMIC_APP_NAME: process.env.TRELLO_APP_NAME || 'COSMIC AI-Risk Management', COSMIC_API_BASE: process.env.PUBLIC_BASE_URL || `http://localhost:${port}` }; res .type('application/javascript') .setHeader('Cache-Control', 'no-store') .send( Object.entries(config) .map(([key, value]) => `window.${key} = ${JSON.stringify(value)};`) .join('\n') ); }); /* Serve static files only after Helmet and iframe-safe headers are configured. The previous version served static files before removing X-Frame-Options. */ app.use(express.static(publicDir)); app.get('/api/health', (_req, res) => { res.json({ ok: true, service: 'cosmic-ai-risk-trello-powerup', version: '0.3.1' }); }); app.get('/api/taxonomy', (_req, res) => { res.json(getReferenceTaxonomy()); }); app.post('/api/boards/:boardId', (req, res) => { const board = upsertBoard(req.params.boardId, req.body || {}); res.status(200).json(board); }); app.post('/api/cards/:cardId', (req, res) => { const card = upsertCard(req.params.cardId, req.body || {}); res.status(200).json(card); }); app.post('/api/risks/score', (req, res) => { const assessment = scoreRiskAssessment(req.body || {}); res.json(assessment); }); app.post('/api/boards/:boardId/cards/:cardId/assessments', (req, res) => { const score = scoreRiskAssessment(req.body || {}); const assessment = saveAssessment({ boardId: req.params.boardId, cardId: req.params.cardId, input: req.body, score }); res.status(201).json(assessment); }); app.get('/api/boards/:boardId/summary', (req, res) => { const assessments = listAssessmentsByBoard(req.params.boardId); const latestByCard = new Map(); for (const assessment of assessments) { latestByCard.set(assessment.cardId, assessment); } const latest = [...latestByCard.values()]; const statusCounts = latest.reduce((acc, item) => { const status = item.score?.status || 'unknown'; acc[status] = (acc[status] || 0) + 1; return acc; }, {}); const averageScore = latest.length ? Math.round(latest.reduce((sum, item) => sum + (item.score?.score || 0), 0) / latest.length) : 0; res.json({ boardId: req.params.boardId, cardCount: latest.length, averageScore, statusCounts, deploymentBlocked: latest.filter((item) => item.score?.gate?.deploymentReady === false).length, latestAssessments: latest }); }); app.get('/api/cards/:cardId/assessments', (req, res) => { res.json(listAssessmentsByCard(req.params.cardId)); }); app.listen(port, () => { console.log(`COSMIC AI-Risk Power-Up running at http://localhost:${port}`); });