feat(links): /api/kutt proxy (version + create + recent)

This commit is contained in:
root
2026-06-08 23:28:07 +10:00
parent c8b9dddd61
commit cd5ca03d96
3 changed files with 79 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ import { router as littleblueRouter } from './routes/littleblue.js';
import { router as aiUsageRouter } from './routes/ai_usage.js';
import { router as infraRouter } from './routes/infra.js';
import { router as clusterRouter } from './routes/cluster.js';
import { router as kuttRouter } from './routes/kutt.js';
export function mountApi(app) {
const api = Router();
@@ -65,6 +66,7 @@ export function mountApi(app) {
api.use('/conversations/:conversation_id/messages', messagesByConvRouter);
api.use('/tags', tagsRouter);
api.use('/links', linksRouter);
api.use('/kutt', kuttRouter);
api.use('/pending-changes', pendingChangesRouter);
api.use('/audit', auditRouter);
api.use('/search', searchRouter);

37
lib/api/routes/kutt.js Normal file
View File

@@ -0,0 +1,37 @@
import { Router } from 'express';
import { z } from 'zod';
import { asyncWrap } from '../errors.js';
import { requireOwner } from '../cap.js';
import { validate } from '../validate.js';
import { compareVersions, fetchLatestKuttRelease, createLink, recentLinks } from '../../links/kutt.js';
export const router = Router();
const cfg = () => ({ base: process.env.KUTT_API_URL, key: process.env.KUTT_API_KEY });
// GET /kutt/version — running (pinned env) vs latest GitHub release (cached 6h).
let cache = { at: 0, val: null };
router.get('/version', requireOwner, asyncWrap(async (_req, res) => {
const running = process.env.KUTT_VERSION || 'unknown';
if (Date.now() - cache.at > 6 * 3600e3 || !cache.val) {
try { cache = { at: Date.now(), val: await fetchLatestKuttRelease({}) }; }
catch { return res.json({ running, latest: null, updateAvailable: false, error: 'version check unavailable' }); }
}
res.json({ ...compareVersions(running, cache.val.latest), url: cache.val.url });
}));
const linkBody = z.object({
target: z.string().url(),
customurl: z.string().max(64).optional(),
description: z.string().max(200).optional()
});
// POST /kutt — create via Kutt (owner). Key stays server-side.
router.post('/', requireOwner, validate({ body: linkBody }), asyncWrap(async (req, res) => {
if (!process.env.KUTT_API_KEY) return res.status(502).json({ error: { code: 'kutt_unconfigured' } });
res.status(201).json(await createLink(req.body, cfg()));
}));
// GET /kutt/recent — last few links (owner).
router.get('/recent', requireOwner, asyncWrap(async (_req, res) => {
res.json(await recentLinks(cfg()));
}));