Barazo AppView backend barazo.forum
at main 102 lines 3.0 kB view raw
1import { sql } from 'drizzle-orm' 2import pino from 'pino' 3import { createDb } from '../src/db/index.js' 4import type { Database } from '../src/db/index.js' 5import type { Logger } from '../src/lib/logger.js' 6 7// --------------------------------------------------------------------------- 8// Types 9// --------------------------------------------------------------------------- 10 11export interface BackfillDeps { 12 db: Database 13 logger: Logger 14} 15 16export interface BackfillResult { 17 updated: number 18} 19 20// --------------------------------------------------------------------------- 21// Core logic (testable) 22// --------------------------------------------------------------------------- 23 24/** 25 * Backfill reply depth using a recursive CTE. 26 * 27 * Direct replies to a topic (parent_uri = root_uri) get depth 1. 28 * Nested replies get parent_depth + 1. 29 * 30 * Idempotent: safe to run multiple times. Overwrites existing depth values 31 * with correct computed values. 32 */ 33export async function backfillReplyDepth(deps: BackfillDeps): Promise<BackfillResult> { 34 const { db, logger } = deps 35 36 logger.info('Starting reply depth backfill') 37 38 const result = await db.execute(sql` 39 WITH RECURSIVE reply_tree AS ( 40 -- Base case: direct replies to topic (depth 1) 41 SELECT uri, parent_uri, root_uri, 1 AS computed_depth 42 FROM replies 43 WHERE parent_uri = root_uri 44 45 UNION ALL 46 47 -- Recursive case: nested replies 48 SELECT r.uri, r.parent_uri, r.root_uri, rt.computed_depth + 1 49 FROM replies r 50 INNER JOIN reply_tree rt ON r.parent_uri = rt.uri 51 WHERE r.parent_uri != r.root_uri 52 ) 53 UPDATE replies 54 SET depth = reply_tree.computed_depth 55 FROM reply_tree 56 WHERE replies.uri = reply_tree.uri 57 AND replies.depth != reply_tree.computed_depth 58 `) 59 60 const updated = Number(result.rowCount ?? 0) 61 62 logger.info({ updated }, 'Reply depth backfill complete') 63 64 return { updated } 65} 66 67// --------------------------------------------------------------------------- 68// CLI entry point 69// --------------------------------------------------------------------------- 70 71async function main(): Promise<void> { 72 const logger = pino({ level: 'info' }) 73 74 const databaseUrl = process.env['DATABASE_URL'] 75 if (!databaseUrl) { 76 logger.fatal('DATABASE_URL environment variable is required') 77 process.exit(1) 78 } 79 80 const { db, client } = createDb(databaseUrl) 81 82 try { 83 const result = await backfillReplyDepth({ db, logger }) 84 logger.info({ updated: result.updated }, 'Backfill complete') 85 } finally { 86 await client.end() 87 } 88 89 process.exit(0) 90} 91 92// Only run main when executed directly via tsx (not imported by Vitest) 93const isDirectExecution = 94 process.argv[1]?.endsWith('backfill-reply-depth.ts') === true && 95 typeof process.env['VITEST'] === 'undefined' 96if (isDirectExecution) { 97 main().catch((err: unknown) => { 98 // eslint-disable-next-line no-console -- CLI fallback for fatal errors before logger setup 99 console.error('Backfill failed:', err) 100 process.exit(1) 101 }) 102}