Files
waoowaoo/scripts/media-backfill-refs.ts

123 lines
3.4 KiB
TypeScript

import { logInfo as _ulogInfo, logError as _ulogError } from '@/lib/logging/core'
import { prisma } from '@/lib/prisma'
import { resolveMediaRefFromLegacyValue } from '@/lib/media/service'
import { MEDIA_MODEL_MAPPINGS } from './media-mapping'
const BATCH_SIZE = 200
type DynamicModel = {
findMany: (args: unknown) => Promise<Array<Record<string, unknown>>>
update: (args: unknown) => Promise<unknown>
}
const prismaDynamic = prisma as unknown as Record<string, DynamicModel>
function toSelect(fields: string[]) {
const select: Record<string, true> = { id: true }
for (const field of fields) select[field] = true
return select
}
async function backfillModel(mapping: (typeof MEDIA_MODEL_MAPPINGS)[number]) {
const model = prismaDynamic[mapping.model]
if (!model) {
throw new Error(`Prisma model not found: ${mapping.model}`)
}
const selectFields = mapping.fields.flatMap((f) => [f.legacyField, f.mediaIdField])
const select = toSelect(selectFields)
let cursor: string | null = null
let scanned = 0
let updated = 0
try {
while (true) {
const rows = await model.findMany({
select,
...(cursor
? {
cursor: { id: cursor },
skip: 1,
}
: {}),
orderBy: { id: 'asc' },
take: BATCH_SIZE,
})
if (!rows.length) break
for (const row of rows) {
scanned += 1
const patch: Record<string, string> = {}
for (const field of mapping.fields) {
const mediaId = row[field.mediaIdField]
const legacyValue = row[field.legacyField]
if (mediaId || typeof legacyValue !== 'string' || !legacyValue.trim()) {
continue
}
const media = await resolveMediaRefFromLegacyValue(legacyValue)
if (!media) continue
patch[field.mediaIdField] = media.id
}
if (Object.keys(patch).length > 0) {
await model.update({
where: { id: String(row.id) },
data: patch,
})
updated += 1
}
}
cursor = String(rows[rows.length - 1].id)
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
if (message.includes('does not exist') || message.includes('Unknown column')) {
_ulogError(
`[media-backfill-refs] skip ${mapping.tableName}: migration columns not available yet`,
message,
)
return { scanned: 0, updated: 0, skipped: true }
}
throw error
}
return { scanned, updated, skipped: false }
}
async function main() {
const startedAt = new Date()
_ulogInfo(`[media-backfill-refs] started at ${startedAt.toISOString()}`)
let totalScanned = 0
let totalUpdated = 0
for (const mapping of MEDIA_MODEL_MAPPINGS) {
const result = await backfillModel(mapping)
totalScanned += result.scanned
totalUpdated += result.updated
if (result.skipped) {
_ulogInfo(`[media-backfill-refs] ${mapping.tableName}: skipped (run add-only DB migration first)`)
} else {
_ulogInfo(
`[media-backfill-refs] ${mapping.tableName}: scanned=${result.scanned} updatedRows=${result.updated}`,
)
}
}
_ulogInfo(
`[media-backfill-refs] done scanned=${totalScanned} updatedRows=${totalUpdated} durationMs=${Date.now() - startedAt.getTime()}`,
)
}
main()
.catch((error) => {
_ulogError('[media-backfill-refs] failed:', error)
process.exitCode = 1
})
.finally(async () => {
await prisma.$disconnect()
})