42 lines
2.8 KiB
JavaScript
42 lines
2.8 KiB
JavaScript
// Explicit, reviewed identity repair. Default is read-only; no department-name mapping.
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { parseArgs } from 'node:util'
|
|
|
|
const { values } = parseArgs({ options: {
|
|
'association-id': { type: 'string' }, 'organization-unit-id': { type: 'string' },
|
|
'expected-pack-id': { type: 'string' }, 'expected-old-path': { type: 'string' },
|
|
'backup-dir': { type: 'string' }, apply: { type: 'boolean', default: false },
|
|
} })
|
|
for (const key of ['association-id', 'organization-unit-id', 'expected-pack-id', 'expected-old-path']) {
|
|
if (!values[key]) throw new Error(`Missing --${key}`)
|
|
}
|
|
if (values.apply && !values['backup-dir']) throw new Error('--apply requires --backup-dir')
|
|
const db = new PrismaClient()
|
|
try {
|
|
const row = await db.materialPermissionPackDepartment.findUnique({ where: { id: values['association-id'] } })
|
|
const unit = await db.organizationUnit.findUnique({ where: { id: values['organization-unit-id'] } })
|
|
if (!row || !unit?.isActive || row.packId !== values['expected-pack-id'] || row.departmentPath !== values['expected-old-path']) throw new Error('Reviewed association or current active department differs; stopped')
|
|
if (row.organizationUnitId && row.organizationUnitId !== unit.id) throw new Error('Association already bound to another identity; stopped')
|
|
const conflict = await db.materialPermissionPackDepartment.findUnique({ where: { organizationUnitId: unit.id } })
|
|
if (conflict && conflict.id !== row.id) throw new Error('Department identity already assigned; stopped')
|
|
const plan = { before: row, after: { organizationUnitId: unit.id, departmentPath: unit.path }, sourceDeptId: unit.sourceDeptId }
|
|
console.log(JSON.stringify({ mode: values.apply ? 'apply' : 'dry-run', ...plan }, null, 2))
|
|
if (values.apply) {
|
|
const dir = path.resolve(values['backup-dir'])
|
|
await mkdir(dir, { recursive: true, mode: 0o700 })
|
|
await writeFile(path.join(dir, `permission-department-${row.id}-${Date.now()}.json`), JSON.stringify(plan, null, 2), { mode: 0o600, flag: 'wx' })
|
|
await db.$transaction(async (tx) => {
|
|
const currentUnit = await tx.organizationUnit.findUnique({ where: { id: unit.id } })
|
|
if (!currentUnit?.isActive || currentUnit.path !== unit.path) throw new Error('Department changed during repair; stopped')
|
|
const result = await tx.materialPermissionPackDepartment.updateMany({
|
|
where: { id: row.id, packId: row.packId, departmentPath: row.departmentPath, organizationUnitId: row.organizationUnitId },
|
|
data: plan.after,
|
|
})
|
|
if (result.count !== 1) throw new Error('Association changed during repair; stopped')
|
|
}, { isolationLevel: 'Serializable' })
|
|
console.log('Verified identity repair applied; backup retained')
|
|
}
|
|
} finally { await db.$disconnect() }
|