/home/techb158/trellopowerup.abdallabala.com/src
NameSizeModeActions
assessmentAnalytics.js71440644editdlrm
riskEngine.js57560644editdlrm
server.js70790644editdlrm
store.js35450644editdlrm
Edit: /home/techb158/trellopowerup.abdallabala.com/src/server.js (7079B)
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 { buildBoardCsv, buildBoardReviewQueueFromSummary, buildBoardSummaryFromAssessments, buildReviewQueueCsv, decorateAssessment, withCardHistoryTrends } from './assessmentAnalytics.js'; import { getReferenceTaxonomy, scoreRiskAssessment } from './riskEngine.js'; import { deleteAssessmentsByCard, getStoreInfo, listAssessmentsByBoard, listAssessmentsByBoardCard, 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 appVersion = '0.4.5'; const staleDaysSetting = Number(process.env.COSMIC_REVIEW_STALE_DAYS || 30); const reviewStaleDays = Number.isFinite(staleDaysSetting) && staleDaysSetting > 0 ? staleDaysSetting : 30; const MS_PER_DAY = 24 * 60 * 60 * 1000; 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; }); } function analyticsOptions() { return { reviewStaleDays }; } function buildBoardSummary(boardId) { return buildBoardSummaryFromAssessments(boardId, listAssessmentsByBoard(boardId), analyticsOptions()); } 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: appVersion, store: getStoreInfo() }); }); 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 previous = listAssessmentsByBoardCard(req.params.boardId, req.params.cardId)[0]; const assessment = saveAssessment({ boardId: req.params.boardId, cardId: req.params.cardId, input: req.body, score }); res.status(201).json(decorateAssessment(assessment, previous, analyticsOptions())); }); app.delete('/api/boards/:boardId/cards/:cardId/assessments', (req, res) => { res.json(deleteAssessmentsByCard(req.params.boardId, req.params.cardId)); }); app.get('/api/boards/:boardId/cards/:cardId/assessments/latest', (req, res) => { const assessments = listAssessmentsByBoardCard(req.params.boardId, req.params.cardId); res.json({ assessment: decorateAssessment(assessments[0], assessments[1], analyticsOptions()) }); }); app.get('/api/boards/:boardId/cards/:cardId/assessments', (req, res) => { res.json(withCardHistoryTrends(listAssessmentsByBoardCard(req.params.boardId, req.params.cardId), analyticsOptions())); }); app.get('/api/boards/:boardId/summary', (req, res) => { res.json(buildBoardSummary(req.params.boardId)); }); app.get('/api/boards/:boardId/review-queue', (req, res) => { res.json(buildBoardReviewQueueFromSummary(buildBoardSummary(req.params.boardId))); }); app.get('/api/boards/:boardId/review-queue.csv', (req, res) => { const queue = buildBoardReviewQueueFromSummary(buildBoardSummary(req.params.boardId)); res .type('text/csv') .setHeader('Content-Disposition', `attachment; filename="cosmic-review-queue-${req.params.boardId}.csv"`) .send(buildReviewQueueCsv(queue)); }); app.get('/api/boards/:boardId/export.json', (req, res) => { const summary = buildBoardSummary(req.params.boardId); res .type('application/json') .setHeader('Content-Disposition', `attachment; filename="cosmic-risk-${req.params.boardId}.json"`) .json({ reportType: 'cosmic-board-risk-export', version: appVersion, ...summary }); }); app.get('/api/boards/:boardId/export.csv', (req, res) => { const summary = buildBoardSummary(req.params.boardId); res .type('text/csv') .setHeader('Content-Disposition', `attachment; filename="cosmic-risk-${req.params.boardId}.csv"`) .send(buildBoardCsv(summary)); }); app.get('/api/cards/:cardId/assessments/latest', (req, res) => { const assessments = listAssessmentsByCard(req.params.cardId); res.json({ assessment: decorateAssessment(assessments[0], assessments[1], analyticsOptions()) }); }); app.get('/api/cards/:cardId/assessments', (req, res) => { res.json(withCardHistoryTrends(listAssessmentsByCard(req.params.cardId), analyticsOptions())); }); app.listen(port, () => { console.log(`COSMIC AI-Risk Power-Up running at http://localhost:${port}`); });