import { Router } from 'express'; import { isValidToken } from './db.js'; import { listArticles, getArticle } from './db.js'; const router = Router(); function escapeHtml(s: unknown): string { return String(s ?? '') .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function page(title: string, body: string): string { return ` ${escapeHtml(title)} ${body}`; } // Browser navigation can't set an Authorization header, so the web UI takes // the token as a query param instead. The API (used by the extension) keeps // using the Bearer header — see auth.ts. function requireTokenQuery(req: any, res: any, next: any) { const token = typeof req.query.t === 'string' ? req.query.t : ''; if (!token || !isValidToken(token)) { res.status(401).send(page('Unauthorized', '

Missing or invalid ?t= token.

')); return; } req.token = token; next(); } router.get('/', requireTokenQuery, (req: any, res) => { const unread = req.query.unread === 'true'; const q = typeof req.query.q === 'string' ? req.query.q.trim() : ''; const articles = listArticles({ unread, q: q || undefined, limit: 100 }); const t = req.token; const items = articles.map(a => `
  • ${escapeHtml(a.title)}
    ${escapeHtml(new URL(a.url).hostname)} — captured ${escapeHtml(a.captured_at.slice(0, 10))}${a.read_at ? '' : ' · unread'}
  • `).join(''); res.send(page('stash', `

    stash

    `)); }); router.get('/read/:id', requireTokenQuery, (req: any, res) => { const article = getArticle(Number(req.params.id)); if (!article) { res.status(404).send(page('Not found', '

    Not found.

    ')); return; } res.send(page(article.title, `

    ← back

    ${escapeHtml(article.title)}

    ${escapeHtml(article.url)} ${article.author ? ` — ${escapeHtml(article.author)}` : ''}

    ${article.content_html}
    `)); }); export default router;