diff --git a/scripts/promote-public-documents-batch.ts b/scripts/promote-public-documents-batch.ts index 80e5a8b9d..eba457a06 100644 --- a/scripts/promote-public-documents-batch.ts +++ b/scripts/promote-public-documents-batch.ts @@ -4,6 +4,14 @@ loadEnvConfig(process.cwd()); const BATCH_SIZE = 25; +function withPublicCorpusMetadata(metadata: unknown): Record { + const base = + typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) + ? (metadata as Record) + : {}; + return { ...base, public_corpus: true }; +} + async function loadAdminClient() { const { createAdminClient } = await import("@/lib/supabase/admin"); return createAdminClient(); @@ -25,7 +33,6 @@ async function fetchBatchIds(supabase: Awaited>clinical_validation_status", ["locally_reviewed", "approved"]) .limit(BATCH_SIZE); if (error) throw new Error(error.message); return (data ?? []).map((row) => row.id as string); @@ -34,14 +41,37 @@ async function fetchBatchIds(supabase: Awaited>, ids: string[]) { if (ids.length === 0) return; - const { error } = await supabase - .from("documents") - .update({ - owner_id: null, - updated_at: new Date().toISOString(), - }) - .in("id", ids); - if (error) throw new Error(error.message); + const updatedAt = new Date().toISOString(); + const { data: documents, error: fetchError } = await supabase.from("documents").select("id, metadata").in("id", ids); + if (fetchError) throw new Error(fetchError.message); + + await Promise.all( + (documents ?? []).map(async (document) => { + const { error } = await supabase + .from("documents") + .update({ + owner_id: null, + metadata: withPublicCorpusMetadata(document.metadata), + updated_at: updatedAt, + }) + .eq("id", document.id); + if (error) throw new Error(error.message); + }), + ); + + const artifactUpdates = await Promise.all([ + supabase.from("document_labels").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + supabase.from("document_summaries").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + supabase.from("document_sections").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + supabase.from("document_memory_cards").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + supabase.from("document_table_facts").update({ owner_id: null }).in("document_id", ids), + supabase.from("document_embedding_fields").update({ owner_id: null }).in("document_id", ids), + supabase.from("document_index_quality").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + supabase.from("document_index_units").update({ owner_id: null, updated_at: updatedAt }).in("document_id", ids), + ]); + for (const { error } of artifactUpdates) { + if (error) throw new Error(error.message); + } } async function main() { diff --git a/scripts/promote-public-documents.ts b/scripts/promote-public-documents.ts index e7b1b606d..4242c906c 100644 --- a/scripts/promote-public-documents.ts +++ b/scripts/promote-public-documents.ts @@ -34,8 +34,7 @@ async function countCandidates(supabase: Awaited>clinical_validation_status", ["locally_reviewed", "approved"]); + .not("owner_id", "is", null); if (ownerId) query = query.eq("owner_id", ownerId); @@ -70,7 +69,7 @@ async function main() { candidateCount, ); console.log( - "Apply migration 20260705220000_promote_locally_reviewed_documents_public.sql with `npx supabase db push --linked`.", + "Promote remaining owned indexed documents with `npm run promote:public-documents:batch` or apply migration 20260706120000_promote_remaining_indexed_documents_public.sql.", ); } diff --git a/supabase/migrations/20260706120000_promote_remaining_indexed_documents_public.sql b/supabase/migrations/20260706120000_promote_remaining_indexed_documents_public.sql new file mode 100644 index 000000000..36edb7343 --- /dev/null +++ b/supabase/migrations/20260706120000_promote_remaining_indexed_documents_public.sql @@ -0,0 +1,64 @@ +-- Promote all remaining indexed owned documents to the public corpus (owner_id IS NULL) +-- regardless of clinical_validation_status, so anonymous callers can access the full indexed corpus. + +begin; + +create temporary table promoted_public_documents on commit drop as +with promoted as ( + update public.documents d + set + owner_id = null, + metadata = jsonb_set( + coalesce(d.metadata, '{}'::jsonb), + '{public_corpus}', + 'true'::jsonb, + true + ), + updated_at = now() + where d.status = 'indexed' + and d.owner_id is not null + returning d.id, d.owner_id as previous_owner_id +) +select id, previous_owner_id from promoted; + +update public.document_labels dl +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where dl.document_id = pd.id; + +update public.document_summaries ds +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where ds.document_id = pd.id; + +update public.document_sections ds +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where ds.document_id = pd.id; + +update public.document_memory_cards dmc +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where dmc.document_id = pd.id; + +update public.document_table_facts dtf +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where dtf.document_id = pd.id; + +update public.document_embedding_fields def +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where def.document_id = pd.id; + +update public.document_index_quality diq +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where diq.document_id = pd.id; + +update public.document_index_units diu +set owner_id = null, updated_at = now() +from promoted_public_documents pd +where diu.document_id = pd.id; + +commit;