Files
doodle-store/apps/server/scripts/import-worksheets.mjs

99 lines
3.3 KiB
JavaScript

import 'reflect-metadata';
import { createHash, randomUUID } from 'node:crypto';
import {
closeSync,
openSync,
readFileSync,
statSync,
unlinkSync,
writeFileSync,
} from 'node:fs';
import { basename, resolve } from 'node:path';
import { createRequire } from 'node:module';
import { parseArgs, parseEnv } from 'node:util';
const require = createRequire(import.meta.url);
const { PrismaService } = require('../dist/database/prisma.service.js');
const { WorksheetImporter } = require('../dist/imports/worksheet-import.js');
const { HttpException } = require('@nestjs/common');
let database;
let reportFd;
let reportPath;
let reportWritten = false;
let committed = false;
try {
const { values } = parseArgs({
options: {
file: { type: 'string' },
apply: { type: 'boolean', default: false },
'metadata-only': { type: 'boolean', default: false },
report: { type: 'string' },
},
});
if (!values.file)
throw new Error('请用 --file 指定题型导出;默认预演,--apply 才写库');
const file = resolve(values.file);
if (statSync(file).size > 10 * 1024 * 1024)
throw new Error('题型文件不能超过 10 MiB');
const source = readFileSync(file, 'utf8');
const sourceSha256 = createHash('sha256').update(source).digest('hex');
const local = parseEnv(
readFileSync(new URL('../.env', import.meta.url), 'utf8'),
);
const url = new URL(process.env.DATABASE_URL ?? local.DATABASE_URL);
// 元数据暂存仅限本地开发库,不能带着未就绪的预览图导入到生产。
if (
!['127.0.0.1', 'localhost'].includes(url.hostname) ||
url.pathname !== '/doodle_dev'
)
throw new Error('题型导入工具只允许本机 doodle_dev');
if (values.report) {
reportPath = resolve(values.report);
reportFd = openSync(reportPath, 'wx');
}
database = new PrismaService({ databaseUrl: url.toString() });
const requestId = `worksheets-import-${randomUUID()}`;
const result = await new WorksheetImporter(database).importExport(source, {
dryRun: !values.apply,
metadataOnly: values['metadata-only'],
requestId,
sourceSha256,
});
committed = !result.dryRun;
const report = {
source: basename(file),
sourceSha256,
requestId,
...result,
};
const output = `${JSON.stringify(report, null, 4)}\n`;
process.stdout.write(output);
if (reportFd !== undefined) {
writeFileSync(reportFd, output);
reportWritten = true;
}
} catch (error) {
const message =
error instanceof HttpException
? error.message
: error instanceof Error &&
!/(postgres|password|connection|prisma|\/\/)/i.test(
error.message,
)
? error.message
: '题型导入失败,请检查本地数据库配置和文件';
process.stderr.write(`${message}\n`);
if (committed)
process.stderr.write(
'数据库事务已提交;报告保存失败,批次见上方输出及审计。\n',
);
process.exitCode = 1;
} finally {
if (reportFd !== undefined) {
closeSync(reportFd);
if (!reportWritten) unlinkSync(reportPath);
}
await database?.onModuleDestroy();
}