feat: add reset button to delete all likes data

POST /likes/reset deletes all YouTube like posts from the posts
collection, clears the youtubeLikesSeen set, and removes the
baseline and sync metadata. Next sync will re-baseline.

Button is tucked inside a <details> disclosure to prevent
accidental clicks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
svemagie
2026-03-18 22:00:50 +01:00
parent f7cf168cd4
commit 768dc40963
5 changed files with 67 additions and 3 deletions

View File

@@ -95,6 +95,7 @@ export default class YouTubeEndpoint {
protectedRouter.get("/likes/connect", likesController.connect); protectedRouter.get("/likes/connect", likesController.connect);
protectedRouter.post("/likes/disconnect", likesController.disconnect); protectedRouter.post("/likes/disconnect", likesController.disconnect);
protectedRouter.post("/likes/sync", likesController.sync); protectedRouter.post("/likes/sync", likesController.sync);
protectedRouter.post("/likes/reset", likesController.reset);
return protectedRouter; return protectedRouter;
} }

View File

@@ -65,7 +65,7 @@ export const likesController = {
} }
// Flash messages from query params // 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 success = null;
let notice = null; let notice = null;
if (connected) success = response.locals.__("youtube.likes.flash.connected"); if (connected) success = response.locals.__("youtube.likes.flash.connected");
@@ -75,6 +75,9 @@ export const likesController = {
const sk = parseInt(skipped, 10) || 0; const sk = parseInt(skipped, 10) || 0;
success = response.locals.__("youtube.likes.flash.synced", { synced: s, skipped: sk }); 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", { response.render("youtube-likes", {
title: response.locals.__("youtube.likes.title"), 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 * GET /api/likes — public JSON API for synced likes
*/ */

View File

@@ -45,10 +45,14 @@
"seenVideos": "Videos gesehen", "seenVideos": "Videos gesehen",
"likePosts": "Like-Beiträge erstellt", "likePosts": "Like-Beiträge erstellt",
"viewOnYouTube": "Auf YouTube ansehen", "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": { "flash": {
"connected": "YouTube-Konto erfolgreich verbunden. Starte eine Synchronisierung, um bestehende Likes zu erfassen.", "connected": "YouTube-Konto erfolgreich verbunden. Starte eine Synchronisierung, um bestehende Likes zu erfassen.",
"disconnected": "YouTube-Konto getrennt.", "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": { "error": {
"noOAuth": "YouTube OAuth ist nicht konfiguriert. Setze YOUTUBE_OAUTH_CLIENT_ID und YOUTUBE_OAUTH_CLIENT_SECRET." "noOAuth": "YouTube OAuth ist nicht konfiguriert. Setze YOUTUBE_OAUTH_CLIENT_ID und YOUTUBE_OAUTH_CLIENT_SECRET."

View File

@@ -45,10 +45,14 @@
"seenVideos": "videos seen", "seenVideos": "videos seen",
"likePosts": "like posts created", "likePosts": "like posts created",
"viewOnYouTube": "View on YouTube", "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": { "flash": {
"connected": "YouTube account connected successfully. Run a sync to baseline your existing likes.", "connected": "YouTube account connected successfully. Run a sync to baseline your existing likes.",
"disconnected": "YouTube account disconnected.", "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": { "error": {
"noOAuth": "YouTube OAuth is not configured. Set YOUTUBE_OAUTH_CLIENT_ID and YOUTUBE_OAUTH_CLIENT_SECRET." "noOAuth": "YouTube OAuth is not configured. Set YOUTUBE_OAUTH_CLIENT_ID and YOUTUBE_OAUTH_CLIENT_SECRET."

View File

@@ -73,6 +73,15 @@
</form> </form>
{% endcall %} {% endcall %}
{# ── Reset (destructive, hidden in details) ── #}
{% call section({ title: __("youtube.likes.reset") }) %}
{% call details({ summary: __("youtube.likes.resetDescription") }) %}
<form method="post" action="{{ mountPath }}/likes/reset">
{{ button({ type: "submit", text: __("youtube.likes.resetConfirm") }) }}
</form>
{% endcall %}
{% endcall %}
{# ── Recent likes ── #} {# ── Recent likes ── #}
{% call section({ title: __("youtube.likes.recentLikes") }) %} {% call section({ title: __("youtube.likes.recentLikes") }) %}
{% if recentLikes and recentLikes.length > 0 %} {% if recentLikes and recentLikes.length > 0 %}