furniture-3d-system-Wu/furniture-3d-system/scripts/backfill-sku-structural-data.ts

149 lines
4.1 KiB
TypeScript

import 'dotenv/config'
import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
const CONFIRM_TOKEN = 'backfill-sku-structural-data'
function readOption(name: string): string {
const prefix = `${name}=`
const inline = process.argv.find((arg) => arg.startsWith(prefix))
if (inline) return inline.slice(prefix.length)
const index = process.argv.indexOf(name)
if (index === -1) return ''
const value = process.argv[index + 1] || ''
return value.startsWith('--') ? '' : value
}
function databaseIdentity(databaseUrl: string): string {
const parsed = new URL(databaseUrl)
return [
parsed.protocol.replace(/:$/, ''),
decodeURIComponent(parsed.username || ''),
parsed.hostname,
parsed.port || '',
parsed.pathname.replace(/^\//, ''),
parsed.searchParams.get('schema') || '',
].join('|')
}
function backupSha256(): string {
const backupPath = process.env.DATABASE_BACKUP_FILE || process.env.DB_BACKUP_FILE || ''
if (!backupPath) return ''
const absolutePath = path.resolve(process.cwd(), backupPath)
if (!fs.existsSync(absolutePath)) return ''
return crypto.createHash('sha256').update(fs.readFileSync(absolutePath)).digest('hex')
}
function assertApplyGuards() {
if (!process.argv.includes('--apply')) return
if (readOption('--confirm') !== CONFIRM_TOKEN) {
throw new Error(`SKU 结构数据回填真实写库必须显式传入 --confirm=${CONFIRM_TOKEN}。`)
}
const databaseUrl = process.env.DATABASE_URL || ''
if (!databaseUrl) {
throw new Error('SKU 结构数据回填真实写库必须先配置 DATABASE_URL。')
}
const expectedDatabaseIdentity = readOption('--expected-database-identity')
if (!expectedDatabaseIdentity) {
throw new Error('SKU 结构数据回填真实写库必须显式传入 --expected-database-identity。')
}
if (expectedDatabaseIdentity !== databaseIdentity(databaseUrl)) {
throw new Error('SKU 结构数据回填目标库身份和当前 DATABASE_URL 不一致,禁止写库。')
}
const expectedBackupSha256 = readOption('--expected-backup-sha256')
if (!expectedBackupSha256) {
throw new Error('SKU 结构数据回填真实写库必须显式传入 --expected-backup-sha256。')
}
if (expectedBackupSha256 !== backupSha256()) {
throw new Error('SKU 结构数据回填备份 sha256 和当前 DATABASE_BACKUP_FILE 不一致,禁止写库。')
}
}
async function main() {
assertApplyGuards()
const apply = process.argv.includes('--apply')
const [{ prisma }, { readSkuCadDataFromAttributes }, { ensureSkuStructuralData }] =
await Promise.all([
import('../src/lib/prisma'),
import('../src/lib/sku-cad-data'),
import('../src/server/services/sku-cad-import.helper'),
])
const skus = await prisma.productSKU.findMany({
where: { deletedAt: null },
select: {
id: true,
productId: true,
skuCode: true,
attributes: true,
},
orderBy: { createdAt: 'asc' },
})
let scanned = 0
let candidates = 0
let filled = 0
let skipped = 0
try {
for (const sku of skus) {
scanned += 1
const cadData = readSkuCadDataFromAttributes(
(sku.attributes ?? null) as Record<string, unknown> | null
)
if (!cadData) {
skipped += 1
continue
}
const existingPanelsCount = await prisma.panel.count({
where: { skuId: sku.id },
})
if (existingPanelsCount > 0) {
skipped += 1
continue
}
candidates += 1
if (!apply) {
console.log(`[backfill] dry-run candidate ${sku.skuCode}`)
continue
}
await ensureSkuStructuralData({
productId: sku.productId,
skuId: sku.id,
cadSourceData: cadData,
})
filled += 1
console.log(`[backfill] filled ${sku.skuCode}`)
}
} finally {
await prisma.$disconnect()
}
console.log(
JSON.stringify(
{
apply,
scanned,
candidates,
filled,
skipped,
},
null,
2
)
)
}
main()
.catch((error) => {
console.error(error)
process.exit(1)
})