Skip to content

Commit 452e004

Browse files
Miriadyoutubeviews
andcommitted
feat: redesign YouTube stats tracking with Supabase pipeline
- Replace Sanity-based youtubeUpdateTask queue with Supabase youtube_stats table - New 3-phase pipeline: discover → fetch → sync (lib/youtube-stats.ts) - Route reduced from 349 to 54 lines (85% reduction) - Sanity only written to when stats actually change - Remove youtubeUpdateTask schema, types, and config references - Add Supabase migration for youtube_stats table with indexes and RPC - Update cron route to forward ?action= param for phase-specific runs - Add cleanup route to delete existing youtubeUpdateTask documents - Add architecture documentation Co-authored-by: youtubeviews <youtubeviews@miriad.systems>
1 parent 68d7931 commit 452e004

File tree

9 files changed

+772
-415
lines changed

9 files changed

+772
-415
lines changed

app/api/cron/route.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export function GET(request: NextRequest) {
1212
});
1313
}
1414
try {
15-
const url = `${publicURL()}/api/youtube/views`;
15+
// Forward the action param if present (discover, fetch, sync)
16+
const action = request.nextUrl.searchParams.get("action");
17+
const params = action ? `?action=${action}` : "";
18+
const url = `${publicURL()}/api/youtube/views${params}`;
1619
console.log("[CRON] Triggering YouTube views update:", url);
1720
fetch(url, {
1821
method: "POST",

app/api/youtube/cleanup/route.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* One-time cleanup script: Delete all youtubeUpdateTask documents from Sanity.
3+
*
4+
* Run via: POST /api/youtube/cleanup
5+
* Auth: Bearer CRON_SECRET
6+
*
7+
* DELETE THIS FILE after running it once.
8+
*/
9+
export const fetchCache = "force-no-store";
10+
11+
import { createClient } from "next-sanity";
12+
import type { NextRequest } from "next/server";
13+
import { apiVersion, dataset, projectId } from "@/sanity/lib/api";
14+
15+
const sanityWriteClient = createClient({
16+
projectId,
17+
dataset,
18+
apiVersion,
19+
token: process.env.SANITY_API_TOKEN || process.env.SANITY_API_WRITE_TOKEN,
20+
perspective: "published",
21+
useCdn: false,
22+
});
23+
24+
export async function POST(request: NextRequest) {
25+
const authHeader = request.headers.get("authorization");
26+
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
27+
return new Response("Unauthorized", { status: 401 });
28+
}
29+
30+
try {
31+
// Count existing tasks
32+
const count = await sanityWriteClient.fetch(
33+
`count(*[_type == "youtubeUpdateTask"])`
34+
);
35+
console.log(`[CLEANUP] Found ${count} youtubeUpdateTask documents to delete`);
36+
37+
if (count === 0) {
38+
return Response.json({ success: true, deleted: 0, message: "No documents to delete" });
39+
}
40+
41+
// Delete in batches of 100
42+
let totalDeleted = 0;
43+
while (true) {
44+
const tasks: { _id: string }[] = await sanityWriteClient.fetch(
45+
`*[_type == "youtubeUpdateTask"][0...100]{ _id }`
46+
);
47+
48+
if (!tasks || tasks.length === 0) break;
49+
50+
// Use transaction for batch delete
51+
const tx = sanityWriteClient.transaction();
52+
for (const task of tasks) {
53+
tx.delete(task._id);
54+
}
55+
await tx.commit({ visibility: "async" });
56+
57+
totalDeleted += tasks.length;
58+
console.log(`[CLEANUP] Deleted batch of ${tasks.length} (total: ${totalDeleted})`);
59+
}
60+
61+
console.log(`[CLEANUP] Done. Deleted ${totalDeleted} youtubeUpdateTask documents.`);
62+
return Response.json({ success: true, deleted: totalDeleted });
63+
} catch (error) {
64+
console.error("[CLEANUP] Error:", error);
65+
return Response.json(
66+
{ success: false, error: String(error) },
67+
{ status: 500 }
68+
);
69+
}
70+
}

0 commit comments

Comments
 (0)