Skip to content

Commit ebef51c

Browse files
Miriadresearch
andcommitted
fix: address PR #604 review — after(), auth fail-close, stuck detection, researchData field
Co-authored-by: research <research@miriad.systems>
1 parent f98f0e2 commit ebef51c

File tree

3 files changed

+54
-35
lines changed

3 files changed

+54
-35
lines changed

app/api/cron/check-research/route.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
export const fetchCache = 'force-no-store';
2-
export const maxDuration = 60;
2+
export const maxDuration = 300;
33

44
import { type NextRequest } from 'next/server';
5+
import { after } from 'next/server';
56
import { createClient, type SanityClient } from 'next-sanity';
67
import { apiVersion, dataset, projectId } from '@/sanity/lib/api';
78
import { NotebookLMClient } from '@/lib/services/notebooklm/client';
@@ -43,7 +44,7 @@ interface ResearchingDoc {
4344
}>;
4445
cta: string;
4546
};
46-
_createdAt: string;
47+
_updatedAt: string;
4748
}
4849

4950
interface EnrichedScript {
@@ -402,7 +403,7 @@ function buildResearchPayload(
402403
return {
403404
topic: doc.title,
404405
notebookId: doc.researchNotebookId,
405-
createdAt: doc._createdAt,
406+
createdAt: doc._updatedAt,
406407
completedAt: new Date().toISOString(),
407408
sources: sources.map((s) => ({
408409
title: s.title,
@@ -600,16 +601,21 @@ async function processResearchingDoc(
600601
// ---------------------------------------------------------------------------
601602

602603
export async function GET(request: NextRequest) {
604+
const cronSecret = process.env.CRON_SECRET;
605+
if (!cronSecret) {
606+
console.error('[check-research] CRON_SECRET not configured');
607+
return Response.json({ error: 'Server misconfigured' }, { status: 503 });
608+
}
603609
const authHeader = request.headers.get('authorization');
604-
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
610+
if (authHeader !== `Bearer ${cronSecret}`) {
605611
console.error('[check-research] Unauthorized request: invalid authorization header');
606612
return Response.json({ error: 'Unauthorized' }, { status: 401 });
607613
}
608614

609615
try {
610616
const sanity = getSanityWriteClient();
611617

612-
// Query for docs in "researching" status with a notebook ID
618+
// Phase 1: Query all researching docs
613619
const docs = await sanity.fetch<ResearchingDoc[]>(
614620
`*[_type == "automatedVideo" && status == "researching" && defined(researchNotebookId)] {
615621
_id,
@@ -620,7 +626,7 @@ export async function GET(request: NextRequest) {
620626
trendScore,
621627
trendSources,
622628
script,
623-
_createdAt
629+
_updatedAt
624630
}`,
625631
);
626632

@@ -630,17 +636,13 @@ export async function GET(request: NextRequest) {
630636
return Response.json({ success: true, message: 'No docs to process', processed: 0 });
631637
}
632638

633-
// Initialize NotebookLM client
634-
console.log('[check-research] Initializing NotebookLM client...');
635-
const auth = await initAuth();
636-
const nbClient = new NotebookLMClient(auth);
637-
638-
const results: ProcessResult[] = [];
639+
// Phase 2: Separate stuck from active (no NotebookLM needed)
640+
const stuckResults: ProcessResult[] = [];
641+
const activeDocs: ResearchingDoc[] = [];
639642
const now = Date.now();
640643

641644
for (const doc of docs) {
642-
// Stuck detection: flag docs stuck in "researching" for >30 minutes
643-
const docAge = now - new Date(doc._createdAt).getTime();
645+
const docAge = now - new Date(doc._updatedAt).getTime();
644646
if (docAge > STUCK_THRESHOLD_MS) {
645647
console.warn(
646648
`[check-research] Doc ${doc._id} ("${doc.title}") stuck in "researching" for ${Math.round(docAge / 60_000)}min — flagging`,
@@ -652,31 +654,36 @@ export async function GET(request: NextRequest) {
652654
flaggedReason: `Stuck in "researching" for ${Math.round(docAge / 60_000)} minutes (threshold: ${STUCK_THRESHOLD_MS / 60_000}min). Research may have failed.`,
653655
})
654656
.commit();
655-
results.push({ id: doc._id, title: doc.title, status: 'flagged', error: 'Stuck in researching' });
656-
continue;
657+
stuckResults.push({ id: doc._id, title: doc.title, status: 'flagged', error: 'Stuck in researching' });
658+
} else {
659+
activeDocs.push(doc);
657660
}
661+
}
658662

659-
try {
660-
const result = await processResearchingDoc(doc, nbClient, sanity);
661-
results.push(result);
662-
} catch (err) {
663-
console.error(`[check-research] Error processing doc ${doc._id}:`, err);
664-
results.push({
665-
id: doc._id,
666-
title: doc.title,
667-
status: 'error',
668-
error: err instanceof Error ? err.message : String(err),
663+
// Phase 3: Process active docs (needs NotebookLM)
664+
if (activeDocs.length > 0) {
665+
console.log('[check-research] Initializing NotebookLM client...');
666+
const auth = await initAuth();
667+
const nbClient = new NotebookLMClient(auth);
668+
669+
for (const doc of activeDocs) {
670+
after(async () => {
671+
try {
672+
await processResearchingDoc(doc, nbClient, sanity);
673+
} catch (err) {
674+
console.error(`[check-research] after() error for ${doc._id}:`, err);
675+
try {
676+
await sanity.patch(doc._id).set({
677+
status: 'flagged',
678+
flaggedReason: `Research processing error: ${err instanceof Error ? err.message : String(err)}`,
679+
}).commit();
680+
} catch { /* best-effort */ }
681+
}
669682
});
670683
}
671684
}
672685

673-
console.log('[check-research] Done!', JSON.stringify(results));
674-
675-
return Response.json({
676-
success: true,
677-
processed: results.length,
678-
results,
679-
});
686+
return Response.json({ success: true, processing: activeDocs.length, flagged: stuckResults.length });
680687
} catch (err) {
681688
console.error('[check-research] Unexpected error:', err);
682689
return Response.json(

app/api/cron/ingest/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,13 @@ async function createSanityDocuments(
390390
// ---------------------------------------------------------------------------
391391

392392
export async function GET(request: NextRequest) {
393+
const cronSecret = process.env.CRON_SECRET;
394+
if (!cronSecret) {
395+
console.error("[CRON/ingest] CRON_SECRET not configured");
396+
return Response.json({ error: "Server misconfigured" }, { status: 503 });
397+
}
393398
const authHeader = request.headers.get("authorization");
394-
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
399+
if (authHeader !== `Bearer ${cronSecret}`) {
395400
console.error(
396401
"[CRON/ingest] Unauthorized request: invalid authorization header",
397402
);

sanity/schemas/documents/automatedVideo.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export default defineType({
223223
name: 'status',
224224
title: 'Pipeline Status',
225225
type: 'string',
226-
description: 'Current stage in the automated video [REDACTED SECRET: NEXT_PUBLIC_SANITY_DATASET] pipeline',
226+
description: 'Current stage in the automated video production pipeline',
227227
options: {
228228
list: [
229229
{title: '1 - Draft', value: 'draft'},
@@ -333,6 +333,13 @@ export default defineType({
333333
description: 'UUID of the NotebookLM deep research task',
334334
hidden: true,
335335
}),
336+
defineField({
337+
name: 'researchData',
338+
title: 'Research Data',
339+
type: 'text',
340+
description: 'JSON-serialized research payload from NotebookLM',
341+
hidden: true,
342+
}),
336343
defineField({
337344
name: 'trendScore',
338345
title: 'Trend Score',

0 commit comments

Comments
 (0)