Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgBEW6qAwgn2fcQbA2f4ZQ
87 lines
2.6 KiB
TypeScript
87 lines
2.6 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';
|
|
|
|
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,
|
|
};
|
|
res.json(payload);
|
|
});
|
|
|
|
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';
|
|
res.json(db.timeline({ limit, before, unreadOnly }));
|
|
});
|
|
|
|
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;
|
|
}
|