feat(mcp): serveur MCP stdio 7 outils + guide API/MCP + couverture tests
CI / build-and-test (push) Successful in 14m3s
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:
+19
-8
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user