/** * Generate SQL UPDATE statements to backfill external_author and external_author_avatar. * Outputs SQL that you can paste into the Supabase SQL Editor. * * Usage: * 1. Start the API: cd api && bun run dev * 2. Run: bun run scripts/backfill-authors-sql.ts * 3. Copy the output SQL and paste into Supabase SQL Editor */ const SUPABASE_URL = process.env.VITE_SUPABASE_URL || '' const SUPABASE_KEY = process.env.VITE_SUPABASE_ANON_KEY || '' const API_URL = process.env.VITE_API_URL || 'http://localhost:3001' if (!SUPABASE_URL || !SUPABASE_KEY) { console.error('Missing VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY in .env') process.exit(1) } function isExternalUrl(url: string): boolean { return ( url.includes('youtube.com') || url.includes('youtu.be') || url.includes('spotify.com') || url.includes('dailymotion.com') || url.includes('dai.ly') || url.includes('soundcloud.com') ) } function escapeSql(s: string): string { return s.replace(/'/g, "''") } async function main() { // Fetch all podcasts (read-only, anon key is fine) const res = await fetch(`${SUPABASE_URL}/rest/v1/podcasts?select=id,audio_url,external_author,external_author_avatar&order=created_at.desc`, { headers: { apikey: SUPABASE_KEY, Authorization: `Bearer ${SUPABASE_KEY}`, }, }) if (!res.ok) { console.error('Failed to fetch podcasts:', await res.text()) process.exit(1) } const podcasts: { id: string; audio_url: string; external_author: string | null; external_author_avatar: string | null }[] = await res.json() const toUpdate = podcasts.filter(p => isExternalUrl(p.audio_url) && (!p.external_author || !p.external_author_avatar)) console.error(`Found ${podcasts.length} total podcasts, ${toUpdate.length} external to backfill\n`) if (toUpdate.length === 0) { console.error('Nothing to do!') return } const statements: string[] = [] for (const podcast of toUpdate) { try { const metaRes = await fetch(`${API_URL}/api/metadata?url=${encodeURIComponent(podcast.audio_url)}`) if (!metaRes.ok) { console.error(` SKIP ${podcast.id} — API returned ${metaRes.status}`) continue } const meta = await metaRes.json() if (!meta.author && !meta.authorAvatar) { console.error(` SKIP ${podcast.id} — no author data`) continue } const sets: string[] = [] if (meta.author) sets.push(`external_author = '${escapeSql(meta.author)}'`) if (meta.authorAvatar) sets.push(`external_author_avatar = '${escapeSql(meta.authorAvatar)}'`) if (sets.length > 0) { statements.push(`UPDATE public.podcasts SET ${sets.join(', ')} WHERE id = '${podcast.id}';`) console.error(` OK ${podcast.id} — ${meta.author} ${meta.authorAvatar ? '(+avatar)' : '(no avatar)'}`) } await new Promise(r => setTimeout(r, 200)) } catch (err) { console.error(` ERR ${podcast.id} — ${err}`) } } // Output the SQL to stdout console.log('-- Backfill external authors and avatars') console.log('-- Generated by scripts/backfill-authors-sql.ts\n') console.log(statements.join('\n')) console.error(`\nGenerated ${statements.length} UPDATE statements. Paste the output into Supabase SQL Editor.`) } main()