Add cache pinning and health controls
This commit is contained in:
@@ -51,4 +51,50 @@ export function registerAdminToolsRoutes(app, { db, adminAuth, media = null, bac
|
||||
return c.json({ ok: true, minLines, category, offset, limit, total: number(count.rows[0]?.total), songs }, 200, { 'Cache-Control': 'no-store' });
|
||||
});
|
||||
|
||||
app.get('/api/admin/cache/health', adminAuth, async c => {
|
||||
const limit = Math.max(1, Math.min(100, Number(c.req.query('limit')) || 50));
|
||||
const offset = Math.max(0, Math.min(1_000_000, Number(c.req.query('offset')) || 0));
|
||||
const rows = await sql.execute({ sql: "SELECT video_id,status,size,last_access,updated_at,gen,pinned FROM media_cache ORDER BY CASE WHEN status='ready' THEN 0 ELSE 1 END,last_access ASC LIMIT ? OFFSET ?", args: [limit, offset] });
|
||||
const total = await sql.execute('SELECT COUNT(*) AS total FROM media_cache');
|
||||
const grouped = await sql.execute('SELECT status,COUNT(*) AS count,COALESCE(SUM(size),0) AS bytes,COALESCE(SUM(CASE WHEN pinned=1 THEN size ELSE 0 END),0) AS pinnedBytes FROM media_cache GROUP BY status');
|
||||
const bytes = grouped.rows.reduce((sum, row) => sum + number(row.bytes), 0);
|
||||
const pinnedBytes = grouped.rows.reduce((sum, row) => sum + number(row.pinnedBytes), 0);
|
||||
const budget = Math.max(0, Number(process.env.MEDIA_CACHE_MAX_BYTES) || 10 * 1024 ** 3);
|
||||
const pinnedBudget = Math.max(0, Number(process.env.MEDIA_CACHE_PINNED_MAX_BYTES) || 2 * 1024 ** 3);
|
||||
return c.json({ ok: true, budget, pinnedBudget, bytes, pinnedBytes, utilization: budget ? bytes / budget : 0,
|
||||
statuses: grouped.rows.map(row => ({ status: row.status, count: number(row.count), bytes: number(row.bytes) })),
|
||||
total: number(total.rows[0]?.total), limit, offset,
|
||||
media: rows.rows.map(row => ({ id: row.video_id, status: row.status, bytes: number(row.size), lastAccess: number(row.last_access), updatedAt: number(row.updated_at), gen: number(row.gen), pinned: !!row.pinned })) }, 200, { 'Cache-Control': 'no-store' });
|
||||
});
|
||||
|
||||
app.post('/api/admin/cache/:id/evict', adminAuth, async c => {
|
||||
const id = c.req.param('id');
|
||||
if (!/^[A-Za-z0-9_-]{6,64}$/.test(id)) return c.json({ ok: false, error: 'invalid media id' }, 400);
|
||||
const row = await db.getMedia(id);
|
||||
if (!row) return c.json({ ok: false, error: 'media not found' }, 404);
|
||||
if (row.pinned) return c.json({ ok: false, error: 'Unpin this copy before evicting it.' }, 409);
|
||||
if (!media?.evictNow) return c.json({ ok: false, error: 'media cache unavailable' }, 503);
|
||||
await media.evictNow(id);
|
||||
await audit(db, 'admin', 'cache.evict', id, { bytes: number(row.size) });
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post('/api/admin/cache/:id/pin', adminAuth, async c => {
|
||||
const id = c.req.param('id');
|
||||
let body = {}; try { body = await c.req.json(); } catch {}
|
||||
if (body.pinned !== true && body.pinned !== false) return c.json({ ok: false, error: 'pinned must be boolean' }, 400);
|
||||
const target = await sql.execute({ sql: "SELECT size,pinned,status FROM media_cache WHERE video_id=?", args: [id] });
|
||||
const row = target.rows[0];
|
||||
if (!row) return c.json({ ok: false, error: 'media not found' }, 404);
|
||||
if (body.pinned && row.status !== 'ready') return c.json({ ok: false, error: 'Only ready cache copies can be pinned.' }, 409);
|
||||
const pinnedBudget = Math.max(0, Number(process.env.MEDIA_CACHE_PINNED_MAX_BYTES) || 2 * 1024 ** 3);
|
||||
if (body.pinned && !row.pinned) {
|
||||
const totals = await sql.execute('SELECT COALESCE(SUM(size),0) AS bytes FROM media_cache WHERE pinned=1');
|
||||
if (number(totals.rows[0]?.bytes) + number(row.size) > pinnedBudget) return c.json({ ok: false, error: 'Pinned cache byte cap exceeded.' }, 409);
|
||||
}
|
||||
await sql.execute({ sql: 'UPDATE media_cache SET pinned=? WHERE video_id=?', args: [body.pinned ? 1 : 0, id] });
|
||||
await audit(db, 'admin', body.pinned ? 'cache.pin' : 'cache.unpin', id, { bytes: number(row.size) });
|
||||
return c.json({ ok: true, pinned: body.pinned });
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user