diff --git a/index.js b/index.js index 5b50e5b..38d3cbc 100644 --- a/index.js +++ b/index.js @@ -95,6 +95,7 @@ export default class YouTubeEndpoint { protectedRouter.get("/likes/connect", likesController.connect); protectedRouter.post("/likes/disconnect", likesController.disconnect); protectedRouter.post("/likes/sync", likesController.sync); + protectedRouter.post("/likes/reset", likesController.reset); return protectedRouter; } diff --git a/lib/controllers/likes.js b/lib/controllers/likes.js index 475e590..f7048be 100644 --- a/lib/controllers/likes.js +++ b/lib/controllers/likes.js @@ -65,7 +65,7 @@ export const likesController = { } // Flash messages from query params - const { error: qError, connected, disconnected, synced, skipped } = request.query; + const { error: qError, connected, disconnected, synced, skipped, reset } = request.query; let success = null; let notice = null; if (connected) success = response.locals.__("youtube.likes.flash.connected"); @@ -75,6 +75,9 @@ export const likesController = { const sk = parseInt(skipped, 10) || 0; success = response.locals.__("youtube.likes.flash.synced", { synced: s, skipped: sk }); } + if (reset !== undefined) { + notice = response.locals.__("youtube.likes.flash.reset", { count: parseInt(reset, 10) || 0 }); + } response.render("youtube-likes", { title: response.locals.__("youtube.likes.title"), @@ -221,6 +224,49 @@ export const likesController = { } }, + /** + * POST /likes/reset — delete all like posts, seen set, and baseline + */ + async reset(request, response) { + try { + const db = request.app.locals.application.getYoutubeDb?.(); + if (!db) { + return response.status(503).json({ error: "Database not available" }); + } + + const postsCollection = request.app.locals.application.collections?.get("posts"); + + let deletedPosts = 0; + if (postsCollection) { + const result = await postsCollection.deleteMany({ + "properties.post-type": "like", + "properties.youtube-video-id": { $exists: true }, + }); + deletedPosts = result.deletedCount || 0; + } + + const seenResult = await db.collection("youtubeLikesSeen").deleteMany({}); + const deletedSeen = seenResult.deletedCount || 0; + + await db.collection("youtubeMeta").deleteOne({ key: "likes_baseline" }); + await db.collection("youtubeMeta").deleteOne({ key: "likes_sync" }); + + console.log(`[YouTube] Reset: deleted ${deletedPosts} posts, ${deletedSeen} seen entries, cleared baseline`); + + if (request.accepts("json")) { + return response.json({ deletedPosts, deletedSeen }); + } + + response.redirect(`${request.baseUrl}/likes?reset=${deletedPosts}`); + } catch (error) { + console.error("[YouTube] Reset error:", error); + if (request.accepts("json")) { + return response.status(500).json({ error: error.message }); + } + response.redirect(`${request.baseUrl}/likes?error=${encodeURIComponent(error.message)}`); + } + }, + /** * GET /api/likes — public JSON API for synced likes */ diff --git a/locales/de.json b/locales/de.json index aa97160..bd7f286 100644 --- a/locales/de.json +++ b/locales/de.json @@ -45,10 +45,14 @@ "seenVideos": "Videos gesehen", "likePosts": "Like-Beiträge erstellt", "viewOnYouTube": "Auf YouTube ansehen", + "reset": "Zurücksetzen", + "resetDescription": "Alle synchronisierten Like-Beiträge, gesehene Video-IDs und Baseline löschen. Nächste Synchronisierung erstellt eine neue Baseline.", + "resetConfirm": "Alle Likes-Daten löschen", "flash": { "connected": "YouTube-Konto erfolgreich verbunden. Starte eine Synchronisierung, um bestehende Likes zu erfassen.", "disconnected": "YouTube-Konto getrennt.", - "synced": "Synchronisierung abgeschlossen: %{synced} neu, %{skipped} übersprungen." + "synced": "Synchronisierung abgeschlossen: %{synced} neu, %{skipped} übersprungen.", + "reset": "Zurücksetzen abgeschlossen: %{count} Like-Beiträge gelöscht. Baseline gelöscht." }, "error": { "noOAuth": "YouTube OAuth ist nicht konfiguriert. Setze YOUTUBE_OAUTH_CLIENT_ID und YOUTUBE_OAUTH_CLIENT_SECRET." diff --git a/locales/en.json b/locales/en.json index 0634ffa..4ee8e89 100644 --- a/locales/en.json +++ b/locales/en.json @@ -45,10 +45,14 @@ "seenVideos": "videos seen", "likePosts": "like posts created", "viewOnYouTube": "View on YouTube", + "reset": "Reset", + "resetDescription": "Delete all synced like posts, seen video IDs, and baseline. Next sync will re-baseline.", + "resetConfirm": "Delete all likes data", "flash": { "connected": "YouTube account connected successfully. Run a sync to baseline your existing likes.", "disconnected": "YouTube account disconnected.", - "synced": "Sync complete: %{synced} new, %{skipped} skipped." + "synced": "Sync complete: %{synced} new, %{skipped} skipped.", + "reset": "Reset complete: %{count} like posts deleted. Baseline cleared." }, "error": { "noOAuth": "YouTube OAuth is not configured. Set YOUTUBE_OAUTH_CLIENT_ID and YOUTUBE_OAUTH_CLIENT_SECRET." diff --git a/views/youtube-likes.njk b/views/youtube-likes.njk index 0d02164..a88246d 100644 --- a/views/youtube-likes.njk +++ b/views/youtube-likes.njk @@ -73,6 +73,15 @@ {% endcall %} + {# ── Reset (destructive, hidden in details) ── #} + {% call section({ title: __("youtube.likes.reset") }) %} + {% call details({ summary: __("youtube.likes.resetDescription") }) %} +
+ {{ button({ type: "submit", text: __("youtube.likes.resetConfirm") }) }} +
+ {% endcall %} + {% endcall %} + {# ── Recent likes ── #} {% call section({ title: __("youtube.likes.recentLikes") }) %} {% if recentLikes and recentLikes.length > 0 %}