const rateLimit = require("express-rate-limit"); const HomeController = require("../controllers/HomeController"); const DigestController = require("../controllers/DigestController"); const MetricRecommendationController = require("../controllers/MetricRecommendationController"); const MonitorController = require("../controllers/MonitorController"); const ObservationController = require("../controllers/ObservationController"); const WorkspaceLearningController = require("../controllers/WorkspaceLearningController"); const verifyToken = require("../modules/verifyToken"); const { getObservationAccess } = require("../modules/observations/access"); const starterSuggestions = require("../modules/ai/homeSuggestions/onboarding"); const homeSuggestions = require("../modules/ai/homeSuggestions/service"); const { allowedActions } = require("../modules/ai/homeSuggestions/rules"); const apiLimiter = rateLimit({ limit: 100, windowMs: 60 * 1000, }); const testDigestLimiter = rateLimit({ keyGenerator: (req) => `${req.user.id}:${req.params.team_id}`, legacyHeaders: false, limit: 3, standardHeaders: true, windowMs: 60 * 60 * 1000, handler: (req, res) => { const resetTime = req.rateLimit?.resetTime?.getTime?.() || (Date.now() + (60 * 60 * 1000)); const retryAfterSeconds = Math.max(1, Math.ceil((resetTime - Date.now()) / 1000)); const retryAfterMinutes = Math.max(1, Math.ceil(retryAfterSeconds / 60)); res.set("Retry-After", `${retryAfterSeconds}`); return res.status(429).send({ code: "SUMMARY_TEST_RATE_LIMITED", error: `You have sent too many test summaries. Try again in ${retryAfterMinutes} minutes.`, retryAfterSeconds, }); }, }); function sendError(res, error) { const statusCode = error.statusCode || 500; return res.status(statusCode).send({ error: statusCode < 500 && error.message ? error.message : "The request could not be completed", }); } function checkAccess() { return async (req, res, next) => { try { req.observationAccess = await getObservationAccess(req.params.team_id, req.user.id); return next(); } catch (error) { return sendError(res, error); } }; } module.exports = (app) => { const homeController = new HomeController(); const digestController = new DigestController(); const metricRecommendationController = new MetricRecommendationController(); const monitorController = new MonitorController(); const observationController = new ObservationController(); const workspaceLearningController = new WorkspaceLearningController(); const routeAccess = [apiLimiter, verifyToken, checkAccess()]; app.get("/team/:team_id/home", ...routeAccess, async (req, res) => { try { res.set("Cache-Control", "no-store"); const [home, suggestions] = await Promise.all([ homeController.getHome(req.observationAccess), homeSuggestions.readSuggestions(req.observationAccess), ]); const config = await homeSuggestions.settings(req.observationAccess); return res.send({ ...home, suggestions, ...await starterSuggestions.readSuggestions(req.observationAccess, home.content), suggestionActions: allowedActions(req.observationAccess, config.envelope, config.policy), }); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/home/suggestions/refresh", ...routeAccess, async (req, res) => { try { await homeSuggestions.requestRefresh(req.observationAccess, app.get("homeSuggestionsQueue")); return res.status(202).send({ success: true }); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/home/activity", ...routeAccess, async (req, res) => { try { const context = req.body.projectId ? [{ entityType: "project", entityId: req.body.projectId }] : []; await homeSuggestions.recordActivity(req.observationAccess, context); return res.send({ success: true }); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/home/suggestions/:id", ...routeAccess, async (req, res) => { try { res.set("Cache-Control", "no-store"); const suggestions = await homeSuggestions.readSuggestions(req.observationAccess); const suggestion = suggestions.find((item) => item.id === req.params.id); if (!suggestion) return res.status(404).send({ error: "This suggestion is no longer available. Choose another question." }); homeSuggestions.log("selected"); return res.send(suggestion); } catch (error) { return sendError(res, error); } }); app.delete("/team/:team_id/home/memories", ...routeAccess, async (req, res) => { try { await homeSuggestions.changeState(req.observationAccess, req.query.id ? { forgetId: req.query.id } : { forget: true }); return res.send({ success: true }); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/activity", ...routeAccess, async (req, res) => { try { return res.send(await homeController.getActivity(req.observationAccess, req.query)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/activity-counts", ...routeAccess, async (req, res) => { try { return res.send(await homeController.getActivityCounts(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/data-health", ...routeAccess, async (req, res) => { try { return res.send(await homeController.getDataHealth(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/data-health/:issue_id/dismiss", ...routeAccess, async (req, res) => { try { return res.send(await homeController.dismissDataHealth( req.observationAccess, req.params.issue_id )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/alerts", ...routeAccess, async (req, res) => { try { return res.send(await homeController.getAlerts(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/workspace-learning/export", ...routeAccess, async (req, res) => { try { return res.send(await workspaceLearningController.export( req.observationAccess, req.query )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/orchestrator-audit", ...routeAccess, async (req, res) => { try { return res.send(await workspaceLearningController.actionAudit( req.observationAccess, req.query )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/orchestrator-egress-audit", ...routeAccess, async (req, res) => { try { return res.send(await workspaceLearningController.egressAudit( req.observationAccess, req.query )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/observations", ...routeAccess, async (req, res) => { try { return res.send(await observationController.list(req.observationAccess, req.query)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/observations/:observation_id", ...routeAccess, async (req, res) => { try { return res.send(await observationController.detail( req.observationAccess, req.params.observation_id )); } catch (error) { return sendError(res, error); } }); app.put("/team/:team_id/observations/:observation_id/preference", ...routeAccess, async (req, res) => { try { return res.send(await observationController.updatePreference( req.observationAccess, req.params.observation_id, req.body )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observations/:observation_id/feedback", ...routeAccess, async (req, res) => { try { return res.send(await observationController.feedback( req.observationAccess, req.params.observation_id, req.body )); } catch (error) { return sendError(res, error); } }); app.delete("/team/:team_id/observations/:observation_id/feedback", ...routeAccess, async (req, res) => { try { return res.send(await observationController.deleteFeedback( req.observationAccess, req.params.observation_id )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observations/:observation_id/resolve", ...routeAccess, async (req, res) => { try { return res.send(await observationController.setResolved( req.observationAccess, req.params.observation_id, true )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observations/:observation_id/reopen", ...routeAccess, async (req, res) => { try { return res.send(await observationController.setResolved( req.observationAccess, req.params.observation_id, false )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observations/:observation_id/investigate", ...routeAccess, async (req, res) => { try { return res.send(await observationController.investigate( req.observationAccess, req.params.observation_id )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observations/:observation_id/drivers", ...routeAccess, async (req, res) => { try { return res.send(await observationController.drivers( req.observationAccess, req.params.observation_id )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/monitors", ...routeAccess, async (req, res) => { try { return res.send(await monitorController.list(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/monitor-recommendations", ...routeAccess, async (req, res) => { try { return res.send(await metricRecommendationController.list(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.post( "/team/:team_id/monitor-recommendations/:recommendation_id/accept", ...routeAccess, async (req, res) => { try { return res.status(201).send(await metricRecommendationController.accept( req.observationAccess, req.params.recommendation_id, req.body, req.user )); } catch (error) { return sendError(res, error); } } ); app.get( "/team/:team_id/monitor-recommendation-dismissals", ...routeAccess, async (req, res) => { try { return res.send(await metricRecommendationController.listDismissals( req.observationAccess )); } catch (error) { return sendError(res, error); } } ); app.delete( "/team/:team_id/monitor-recommendation-dismissals/:dismissal_id", ...routeAccess, async (req, res) => { try { return res.send(await metricRecommendationController.restore( req.observationAccess, req.params.dismissal_id )); } catch (error) { return sendError(res, error); } } ); app.post( "/team/:team_id/monitor-recommendations/:recommendation_id/dismiss", ...routeAccess, async (req, res) => { try { return res.send(await metricRecommendationController.dismiss( req.observationAccess, req.params.recommendation_id, req.body )); } catch (error) { return sendError(res, error); } } ); app.get("/team/:team_id/charts/:chart_id/monitor-options", ...routeAccess, async (req, res) => { try { return res.send(await monitorController.options( req.observationAccess, req.params.chart_id )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/monitors", ...routeAccess, async (req, res) => { try { return res.status(201).send(await monitorController.create( req.observationAccess, req.body, req.user )); } catch (error) { return sendError(res, error); } }); app.put("/team/:team_id/monitors/:monitor_id", ...routeAccess, async (req, res) => { try { return res.send(await monitorController.updateWithAudit( req.observationAccess, req.params.monitor_id, req.body )); } catch (error) { return sendError(res, error); } }); app.delete("/team/:team_id/monitors/:monitor_id", ...routeAccess, async (req, res) => { try { return res.send(await monitorController.remove( req.observationAccess, req.params.monitor_id )); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/monitors/:monitor_id/refresh", ...routeAccess, async (req, res) => { try { return res.send(await monitorController.refresh( req.observationAccess, req.params.monitor_id, req.user )); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/observation-digests", ...routeAccess, async (req, res) => { try { return res.send(await digestController.list(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.get("/team/:team_id/observation-digests/options", ...routeAccess, async (req, res) => { try { return res.send(await digestController.options(req.observationAccess)); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observation-digests/preview", ...routeAccess, async (req, res) => { try { return res.send(await digestController.preview(req.observationAccess, req.body)); } catch (error) { return sendError(res, error); } }); app.post("/team/:team_id/observation-digests", ...routeAccess, async (req, res) => { try { return res.status(201).send(await digestController.create( req.observationAccess, req.body, )); } catch (error) { return sendError(res, error); } }); app.put( "/team/:team_id/observation-digests/:subscription_id", ...routeAccess, async (req, res) => { try { return res.send(await digestController.updateWithAudit( req.observationAccess, req.params.subscription_id, req.body, )); } catch (error) { return sendError(res, error); } }, ); app.delete( "/team/:team_id/observation-digests/:subscription_id", ...routeAccess, async (req, res) => { try { return res.send(await digestController.remove( req.observationAccess, req.params.subscription_id, )); } catch (error) { return sendError(res, error); } }, ); app.post( "/team/:team_id/observation-digests/:subscription_id/send-test", ...routeAccess, testDigestLimiter, async (req, res) => { try { return res.send(await digestController.sendTest( req.observationAccess, req.params.subscription_id, )); } catch (error) { return sendError(res, error); } }, ); return (req, res, next) => next(); };