wetalk/scripts/backfill-authors-sql.ts
ordinarthur 0f9679ca2f
Some checks failed
Build & Deploy / build-and-deploy (push) Has been cancelled
add good author
2026-04-13 11:45:06 +02:00

98 lines
3.2 KiB
TypeScript

/**
* 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()