Barazo AppView backend
barazo.forum
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}