feat(mcp): serveur MCP stdio 7 outils + guide API/MCP + couverture tests
CI / build-and-test (push) Successful in 14m3s

- mcp/server.mjs : search, suggest, details, trending, transcript,
  transcript_text, health_check (client fin vers API REST)
- docs/API_MCP_GUIDE.md : reference complete REST + MCP + transcript
- tests : server/tests/api_coverage.test.mjs (17 cas HTTP),
  mcp/tools.test.mjs (7 outils sur stub), scripts test:mcp/test:api
- fix(db): DELETE playlist 500 FK -> metrique avant delete + cleanup
  enfants + recordPlaylistMetric tolerant (observabilite)
This commit is contained in:
2026-09-27 18:39:26 -04:00
parent d0d1c07535
commit 1812b86dd4
8 changed files with 1025 additions and 13 deletions
+19 -8
View File
@@ -771,14 +771,20 @@ export function listLikedVideos({ userId, limit = 100, q }) {
}
// -------------------- Playlists --------------------
// Metrics are observability-only: they must never break the core operation
// (e.g. a playlist DELETE must stay 204 even if the metrics insert fails).
export function recordPlaylistMetric({ userId, playlistId, action, meta }) {
const id = cryptoRandomId();
const created_at = nowIso();
const meta_json = meta ? JSON.stringify(meta) : null;
db.prepare(`INSERT INTO playlist_metrics (id, user_id, playlist_id, action, meta_json, created_at)
VALUES (?, ?, ?, ?, ?, ?)`)
.run(id, userId, playlistId, action, meta_json, created_at);
return { id, created_at };
try {
const id = cryptoRandomId();
const created_at = nowIso();
const meta_json = meta ? JSON.stringify(meta) : null;
db.prepare(`INSERT INTO playlist_metrics (id, user_id, playlist_id, action, meta_json, created_at)
VALUES (?, ?, ?, ?, ?, ?)`)
.run(id, userId, playlistId, action, meta_json, created_at);
return { id, created_at };
} catch {
return null;
}
}
export function createPlaylist({ userId, title, description, thumbnail, isPrivate = true }) {
@@ -864,8 +870,13 @@ export function deletePlaylist({ userId, id }) {
const cur = db.prepare(`SELECT * FROM playlists WHERE id = ?`).get(id);
if (!cur) return { removed: false };
if (cur.user_id !== userId) return 'forbidden';
// Record BEFORE the row disappears: playlist_metrics.playlist_id references
// playlists(id), so inserting after the DELETE violates the FK constraint.
try { recordPlaylistMetric({ userId, playlistId: id, action: 'delete' }); } catch {}
// Explicit child cleanup (works even on old DBs whose FK lacks ON DELETE CASCADE).
try { db.prepare(`DELETE FROM playlist_items WHERE playlist_id = ?`).run(id); } catch {}
try { db.prepare(`DELETE FROM playlist_metrics WHERE playlist_id = ?`).run(id); } catch {}
const info = db.prepare(`DELETE FROM playlists WHERE id = ?`).run(id);
recordPlaylistMetric({ userId, playlistId: id, action: 'delete' });
return { removed: (info.changes || 0) > 0 };
}