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
${items || '- Nothing captured yet.
'}
`));
});
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;