pr-monitor/server/routes.ts
2026-07-29 09:24:20 +00:00

94 lines
2.9 KiB
TypeScript

import express, { type Express } from 'express';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import type { StatePayload, SyncEventPayload } from '../shared/types.js';
import type { Db } from './db.js';
import { rootDir } from './config.js';
import { addClient } from './sse.js';
import { buildFailureReport } from './failures.js';
export interface RouteDeps {
db: Db;
triggerSync: () => Promise<void>;
}
export function buildSyncPayload(db: Db, changedPrIds: string[] = [], newEventCount = 0): SyncEventPayload {
return {
lastSyncAt: db.getMeta('last_sync_at'),
error: db.getMeta('last_sync_error'),
changedPrIds,
newEventCount,
unreadCount: db.unreadCounts().total,
};
}
export function createApp({ db, triggerSync }: RouteDeps): Express {
const app = express();
app.use(express.json());
app.get('/api/state', (_req, res) => {
const { byPr, total } = db.unreadCounts();
const payload: StatePayload = {
prs: db.getOpenSnapshots(),
lastSyncAt: db.getMeta('last_sync_at'),
lastSyncError: db.getMeta('last_sync_error'),
unreadCountsByPr: byPr,
unreadTotal: total,
failuresByPr: db.failureSummaries(),
};
res.json(payload);
});
app.get('/api/prs/:id/failures', (req, res) => {
res.json(db.failureReportRows(req.params.id).map(buildFailureReport));
});
app.get('/api/timeline', (req, res) => {
const limit = Math.min(Number(req.query.limit) || 50, 200);
const before = typeof req.query.before === 'string' ? req.query.before : undefined;
const unreadOnly = req.query.unread === '1';
const prId = typeof req.query.pr === 'string' ? req.query.pr : undefined;
res.json(db.timeline({ limit, before, unreadOnly, prId }));
});
app.post('/api/timeline/:id/read', (req, res) => {
db.markRead(req.params.id, true, new Date().toISOString());
res.json({ ok: true });
});
app.post('/api/timeline/:id/unread', (req, res) => {
db.markRead(req.params.id, false, new Date().toISOString());
res.json({ ok: true });
});
app.post('/api/timeline/read-all', (req, res) => {
const before = typeof req.body?.before === 'string' ? req.body.before : undefined;
const changed = db.markAllRead(new Date().toISOString(), before);
res.json({ ok: true, changed });
});
app.post('/api/prs/:id/read-all', (req, res) => {
const changed = db.markAllReadForPr(req.params.id, new Date().toISOString());
res.json({ ok: true, changed });
});
app.post('/api/sync', (_req, res) => {
void triggerSync();
res.json({ ok: true });
});
app.get('/api/events', (_req, res) => {
addClient(res, buildSyncPayload(db));
});
const webDist = join(rootDir, 'web', 'dist');
if (existsSync(webDist)) {
app.use(express.static(webDist));
app.get(/^\/(?!api\/).*/, (_req, res) => {
res.sendFile(join(webDist, 'index.html'));
});
}
return app;
}