Skip to content

Commit e8be005

Browse files
Miriaddistribute
andcommitted
fix: distribute webhook fetches full doc from Sanity instead of relying on webhook payload
The webhook projection only sends { _id, _type, status }, so the route now fetches the full automatedVideo document from Sanity before processing. This is more robust and means the webhook setup doesn't need to include all document fields in the projection. Also adds a re-check of the document status from Sanity to handle race conditions. Co-authored-by: distribute <distribute@miriad.systems>
1 parent 72dedb0 commit e8be005

1 file changed

Lines changed: 40 additions & 13 deletions

File tree

  • app/api/webhooks/sanity-distribute

app/api/webhooks/sanity-distribute/route.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ function isValidSignature(body: string, signature: string | null): boolean {
2222
}
2323
}
2424

25+
// Minimal webhook payload — we fetch the full doc from Sanity
26+
interface WebhookPayload {
27+
_id: string;
28+
_type: string;
29+
status?: string;
30+
}
31+
2532
interface AutomatedVideoDoc {
2633
_id: string;
2734
_type: "automatedVideo";
@@ -75,38 +82,58 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
7582
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
7683
}
7784

78-
let payload: AutomatedVideoDoc;
79-
try { payload = JSON.parse(rawBody); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); }
85+
// Parse the minimal webhook payload (just _id, _type, status)
86+
let webhookPayload: WebhookPayload;
87+
try { webhookPayload = JSON.parse(rawBody); } catch { return NextResponse.json({ error: "Invalid JSON" }, { status: 400 }); }
88+
89+
if (webhookPayload._type !== "automatedVideo") return NextResponse.json({ skipped: true, reason: "Not automatedVideo" });
90+
if (webhookPayload.status !== "video_gen") return NextResponse.json({ skipped: true, reason: `Status "${webhookPayload.status}" != "video_gen"` });
91+
92+
const docId = webhookPayload._id;
8093

81-
if (payload._type !== "automatedVideo") return NextResponse.json({ skipped: true, reason: "Not automatedVideo" });
82-
if (payload.status !== "video_gen") return NextResponse.json({ skipped: true, reason: `Status "${payload.status}" != "video_gen"` });
83-
if (payload.flaggedReason) return NextResponse.json({ skipped: true, reason: "Flagged" });
94+
// Fetch the full document from Sanity (webhook only sends minimal projection)
95+
const doc = await writeClient.fetch<AutomatedVideoDoc | null>(
96+
`*[_id == $id][0]`,
97+
{ id: docId }
98+
);
99+
100+
if (!doc) {
101+
console.error(`[sanity-distribute] Document ${docId} not found in Sanity`);
102+
return NextResponse.json({ error: "Document not found" }, { status: 404 });
103+
}
104+
105+
// Re-check status from the actual document (in case of race condition)
106+
if (doc.status !== "video_gen") {
107+
return NextResponse.json({ skipped: true, reason: `Document status is "${doc.status}", not "video_gen"` });
108+
}
109+
if (doc.flaggedReason) {
110+
return NextResponse.json({ skipped: true, reason: "Flagged" });
111+
}
84112

85-
const docId = payload._id;
86-
console.log(`[sanity-distribute] Processing ${docId}: "${payload.title}"`);
113+
console.log(`[sanity-distribute] Processing ${docId}: "${doc.title}"`);
87114

88115
try {
89116
await updateStatus(docId, "uploading");
90117

91118
// Step 1: Gemini metadata
92-
const metadata = await generateYouTubeMetadata(payload);
119+
const metadata = await generateYouTubeMetadata(doc);
93120

94121
// Step 2: Upload main video
95122
let youtubeVideoId = "";
96-
if (payload.videoUrl) {
97-
const r = await uploadVideo({ videoUrl: payload.videoUrl, title: metadata.title, description: metadata.description, tags: metadata.tags });
123+
if (doc.videoUrl) {
124+
const r = await uploadVideo({ videoUrl: doc.videoUrl, title: metadata.title, description: metadata.description, tags: metadata.tags });
98125
youtubeVideoId = r.videoId;
99126
}
100127

101128
// Step 3: Upload short
102129
let youtubeShortId = "";
103-
if (payload.shortUrl) {
104-
const r = await uploadShort({ videoUrl: payload.shortUrl, title: metadata.title, description: metadata.description, tags: metadata.tags });
130+
if (doc.shortUrl) {
131+
const r = await uploadShort({ videoUrl: doc.shortUrl, title: metadata.title, description: metadata.description, tags: metadata.tags });
105132
youtubeShortId = r.videoId;
106133
}
107134

108135
// Step 4: Email (non-fatal)
109-
const ytUrl = youtubeVideoId ? `https://www.youtube.com/watch?v=${youtubeVideoId}` : payload.videoUrl || "";
136+
const ytUrl = youtubeVideoId ? `https://www.youtube.com/watch?v=${youtubeVideoId}` : doc.videoUrl || "";
110137
try {
111138
await notifySubscribers({ subject: `New Video: ${metadata.title}`, videoTitle: metadata.title, videoUrl: ytUrl, description: metadata.description.slice(0, 280) });
112139
} catch (e) { console.warn("[sanity-distribute] Email error:", e); }

0 commit comments

Comments
 (0)