|
|
|
|
/* eslint-disable no-await-in-loop */
|
|
|
|
|
import mariadb from 'mariadb';
|
|
|
|
|
import {
|
|
|
|
|
buildContent, ContestModel, DiscussionDoc, DiscussionReplyDoc, DocumentModel, DomainModel,
|
|
|
|
|
fs, noop, NotFoundError, ObjectID, PERM, postJudge, ProblemModel, RecordDoc, RecordModel,
|
|
|
|
|
STATUS, SystemModel, Time, UserModel, yaml,
|
|
|
|
|
} from 'hydrooj';
|
|
|
|
|
|
|
|
|
|
const contentTypeMap = {
|
|
|
|
|
noi: 'oi',
|
|
|
|
|
ioi: 'ioi',
|
|
|
|
|
acm: 'acm',
|
|
|
|
|
};
|
|
|
|
|
const statusMap = {
|
|
|
|
|
Accepted: STATUS.STATUS_ACCEPTED,
|
|
|
|
|
'Compile Error': STATUS.STATUS_COMPILE_ERROR,
|
|
|
|
|
'File Error': STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
'Invalid Interaction': STATUS.STATUS_FORMAT_ERROR,
|
|
|
|
|
'Judgement Failed': STATUS.STATUS_SYSTEM_ERROR,
|
|
|
|
|
'Memory Limit Exceeded': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
|
|
|
|
|
'No Testdata': STATUS.STATUS_FORMAT_ERROR,
|
|
|
|
|
'Output Limit Exceeded': STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
|
|
|
|
|
'Partially Correct': STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
'Runtime Error': STATUS.STATUS_RUNTIME_ERROR,
|
|
|
|
|
'System Error': STATUS.STATUS_SYSTEM_ERROR,
|
|
|
|
|
'Time Limit Exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
|
|
|
|
|
Unknown: STATUS.STATUS_SYSTEM_ERROR,
|
|
|
|
|
'Wrong Answer': STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
Waiting: STATUS.STATUS_WAITING,
|
|
|
|
|
Cheated: STATUS.STATUS_CANCELED,
|
|
|
|
|
};
|
|
|
|
|
const TestcaseStatusMap = {
|
|
|
|
|
0: STATUS.STATUS_WAITING,
|
|
|
|
|
1: STATUS.STATUS_JUDGING,
|
|
|
|
|
3: STATUS.STATUS_SYSTEM_ERROR,
|
|
|
|
|
4: STATUS.STATUS_IGNORED,
|
|
|
|
|
};
|
|
|
|
|
const TestcaseJudgeStatusMap = {
|
|
|
|
|
1: STATUS.STATUS_ACCEPTED,
|
|
|
|
|
2: STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
3: STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
4: STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
|
|
|
|
|
5: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
|
|
|
|
|
6: STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
|
|
|
|
|
7: STATUS.STATUS_WRONG_ANSWER,
|
|
|
|
|
8: STATUS.STATUS_RUNTIME_ERROR,
|
|
|
|
|
9: STATUS.STATUS_SYSTEM_ERROR,
|
|
|
|
|
10: STATUS.STATUS_FORMAT_ERROR,
|
|
|
|
|
};
|
|
|
|
|
const sexMap = {
|
|
|
|
|
0: 3,
|
|
|
|
|
1: 1,
|
|
|
|
|
[-1]: 2,
|
|
|
|
|
};
|
|
|
|
|
const langMap = {
|
|
|
|
|
cpp: 'cc.cc98',
|
|
|
|
|
cpp11: 'cc.cc11',
|
|
|
|
|
cpp17: 'cc.cc17',
|
|
|
|
|
'cpp-noilinux': 'cc.cc98',
|
|
|
|
|
'cpp11-noilinux': 'cc.cc11',
|
|
|
|
|
'cpp11-clang': 'cc.cc11',
|
|
|
|
|
'cpp17-clang': 'cc.cc17',
|
|
|
|
|
c: 'c',
|
|
|
|
|
'c-noilinux': 'c',
|
|
|
|
|
csharp: 'cs',
|
|
|
|
|
java: 'java',
|
|
|
|
|
pascal: 'pas',
|
|
|
|
|
python2: 'py.py2',
|
|
|
|
|
python3: 'py.py3',
|
|
|
|
|
nodejs: 'js',
|
|
|
|
|
ruby: 'rb',
|
|
|
|
|
haskell: 'hs',
|
|
|
|
|
};
|
|
|
|
|
export async function run({
|
|
|
|
|
host = 'localhost', port = 3306, name = 'syzoj',
|
|
|
|
|
username, password, domainId, dataDir,
|
|
|
|
|
rerun = true, randomMail = false,
|
|
|
|
|
}, report: Function) {
|
|
|
|
|
const src = await mariadb.createConnection({
|
|
|
|
|
host,
|
|
|
|
|
port,
|
|
|
|
|
user: username,
|
|
|
|
|
password,
|
|
|
|
|
database: name,
|
|
|
|
|
});
|
|
|
|
|
const query = (q: string) => new Promise<any[]>((res, rej) => {
|
|
|
|
|
src.query(q).then((r) => res(r)).catch((e) => rej(e));
|
|
|
|
|
});
|
|
|
|
|
const target = await DomainModel.get(domainId);
|
|
|
|
|
if (!target) throw new NotFoundError(domainId);
|
|
|
|
|
report({ message: 'Connected to database' });
|
|
|
|
|
/*
|
|
|
|
|
`id` int NOT NULL AUTO_INCREMENT, 用户id(主键)
|
|
|
|
|
`username` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, 用户名
|
|
|
|
|
`email` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, 用户E-mail
|
|
|
|
|
`password` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, 密码
|
|
|
|
|
`nickname` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, 昵称
|
|
|
|
|
`nameplate` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`information` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, 个人信息
|
|
|
|
|
`ac_num` int NULL DEFAULT NULL, AC数量
|
|
|
|
|
`submit_num` int NULL DEFAULT NULL, 提交数量
|
|
|
|
|
`is_admin` tinyint NULL DEFAULT NULL, 是否为管理员
|
|
|
|
|
`is_show` tinyint NULL DEFAULT NULL, 是否公开信息
|
|
|
|
|
`public_email` tinyint NULL DEFAULT 1, 是否公开E-mail
|
|
|
|
|
`prefer_formatted_code` tinyint NULL DEFAULT 1, 是否使用格式化代码
|
|
|
|
|
`sex` int NULL DEFAULT NULL, 性别
|
|
|
|
|
`rating` int NULL DEFAULT NULL,
|
|
|
|
|
`register_time` int NULL DEFAULT NULL, 注册时间
|
|
|
|
|
*/
|
|
|
|
|
const uidMap: Record<string, number> = {};
|
|
|
|
|
const superAdmin = [];
|
|
|
|
|
const udocs = await query('SELECT * FROM `user`');
|
|
|
|
|
report({ message: udocs.map((u) => u.username.toLowerCase()) });
|
|
|
|
|
const precheck = await UserModel.getMulti({ unameLower: { $in: udocs.map((u) => u.username.toLowerCase()) } }).toArray();
|
|
|
|
|
if (precheck.length) throw new Error(`Conflict username: ${precheck.map((u) => u.unameLower).join(', ')}`);
|
|
|
|
|
for (const udoc of udocs) {
|
|
|
|
|
if (randomMail) delete udoc.email;
|
|
|
|
|
let current = await UserModel.getByEmail(domainId, udoc.email || `${udoc.username}@syzoj.local`);
|
|
|
|
|
current ||= await UserModel.getByUname(domainId, udoc.username);
|
|
|
|
|
if (current) {
|
|
|
|
|
report({ message: `duplicate user with email ${udoc.email}: ${current.uname},${udoc.username}` });
|
|
|
|
|
uidMap[udoc.id] = current._id;
|
|
|
|
|
} else {
|
|
|
|
|
const uid = await UserModel.create(
|
|
|
|
|
udoc.email || `${udoc.username}@syzoj.local`, udoc.username, '',
|
|
|
|
|
null, udoc.ip, SystemModel.get('default.priv'),
|
|
|
|
|
);
|
|
|
|
|
if (udoc.is_admin) await UserModel.setSuperAdmin(uid);
|
|
|
|
|
superAdmin.push(uid);
|
|
|
|
|
uidMap[udoc.id] = uid;
|
|
|
|
|
await UserModel.setById(uid, {
|
|
|
|
|
regat: new Date(udoc.register_time * 1000),
|
|
|
|
|
hash: udoc.password,
|
|
|
|
|
salt: udoc.password,
|
|
|
|
|
hashType: 'syzoj',
|
|
|
|
|
bio: udoc.information || '',
|
|
|
|
|
gender: sexMap[udoc.sex] || 3,
|
|
|
|
|
});
|
|
|
|
|
await DomainModel.setUserInDomain(domainId, uid, {
|
|
|
|
|
displayName: udoc.nickname || '',
|
|
|
|
|
nSubmit: udoc.submit_num,
|
|
|
|
|
nAccept: udoc.ac_num,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// I think manage_problem_tag is a useless role
|
|
|
|
|
await DomainModel.addRole(domainId, 'manage_problem',
|
|
|
|
|
PERM.PERM_DEFAULT | PERM.PERM_CREATE_PROBLEM | PERM.PERM_EDIT_PROBLEM | PERM.PERM_VIEW_PROBLEM_HIDDEN | PERM.PERM_READ_PROBLEM_DATA
|
|
|
|
|
| PERM.PERM_EDIT_PROBLEM_SOLUTION | PERM.PERM_DELETE_PROBLEM_SOLUTION | PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY);
|
|
|
|
|
await DomainModel.addRole(domainId, 'manage_user', PERM.PERM_DEFAULT | PERM.PERM_EDIT_DOMAIN);
|
|
|
|
|
const privileges = await query('SELECT user_id,group_concat(privilege) as privilege FROM `user_privilege` group by user_id');
|
|
|
|
|
for (const privilege of privileges) {
|
|
|
|
|
if (!superAdmin.includes(privilege.user_id)) {
|
|
|
|
|
if (privilege.privilege.split(',').includes('manage_problem') && privilege.privilege.split(',').includes('manage_user')) {
|
|
|
|
|
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'root');
|
|
|
|
|
} else if (privilege.privilege.split(',').includes('manage_problem')) {
|
|
|
|
|
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'manage_problem');
|
|
|
|
|
} else if (privilege.privilege.split(',').includes('manage_user')) {
|
|
|
|
|
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'manage_user');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
report({ message: 'user finished' });
|
|
|
|
|
|
|
|
|
|
const allTags = await query('SELECT * FROM `problem_tag`');
|
|
|
|
|
const tagMap: Record<number, string> = {};
|
|
|
|
|
for (const tag of allTags) tagMap[tag.id] = tag.name;
|
|
|
|
|
report({ message: 'tag finished' });
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
`id` int NOT NULL AUTO_INCREMENT,
|
|
|
|
|
`title` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
|
|
|
|
`user_id` int NULL DEFAULT NULL,
|
|
|
|
|
`publicizer_id` int NULL DEFAULT NULL,
|
|
|
|
|
`is_anonymous` tinyint NULL DEFAULT NULL,
|
|
|
|
|
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`input_format` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`output_format` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`example` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`limit_and_hint` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`time_limit` int NULL DEFAULT NULL,
|
|
|
|
|
`memory_limit` int NULL DEFAULT NULL,
|
|
|
|
|
`additional_file_id` int NULL DEFAULT NULL,
|
|
|
|
|
`ac_num` int NULL DEFAULT NULL,
|
|
|
|
|
`submit_num` int NULL DEFAULT NULL,
|
|
|
|
|
`is_public` tinyint NULL DEFAULT NULL,
|
|
|
|
|
`file_io` tinyint NULL DEFAULT NULL,
|
|
|
|
|
`file_io_input_name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`file_io_output_name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`publicize_time` datetime NULL DEFAULT NULL, 公开时间
|
|
|
|
|
`type` enum('traditional','submit-answer','interaction') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'traditional',
|
|
|
|
|
*/
|
|
|
|
|
const fileReg = /\[.*\]\((\/problem\/(\d+)\/testdata\/download\/(.*))\)/gm;
|
|
|
|
|
const pidMap: Record<string, number> = {};
|
|
|
|
|
const configMap: Record<string, string> = {};
|
|
|
|
|
const problemAdditionalFile = {};
|
|
|
|
|
const [{ 'count(*)': pcount }] = await query('SELECT count(*) FROM `problem`');
|
|
|
|
|
const step = 50;
|
|
|
|
|
const pageCount = Math.ceil(Number(pcount) / step);
|
|
|
|
|
for (let pageId = 0; pageId < pageCount; pageId++) {
|
|
|
|
|
const pdocs = await query(`SELECT * FROM \`problem\` LIMIT ${pageId * step}, ${step}`);
|
|
|
|
|
for (const pdoc of pdocs) {
|
|
|
|
|
if (rerun) {
|
|
|
|
|
const opdoc = await ProblemModel.get(domainId, `P${pdoc.id}`);
|
|
|
|
|
if (opdoc) pidMap[pdoc.id] = opdoc.docId;
|
|
|
|
|
}
|
|
|
|
|
if (!pidMap[pdoc.id]) {
|
|
|
|
|
let content = buildContent({
|
|
|
|
|
description: pdoc.description,
|
|
|
|
|
input: pdoc.input_format,
|
|
|
|
|
output: `${pdoc.output_format}\n## Sample\n${pdoc.example}`,
|
|
|
|
|
samples: [],
|
|
|
|
|
hint: pdoc.limit_and_hint,
|
|
|
|
|
});
|
|
|
|
|
for (const match of content.matchAll(fileReg)) {
|
|
|
|
|
const [, origialPath, pid, filename] = match;
|
|
|
|
|
if (!problemAdditionalFile[`P${pdoc.id}`]) problemAdditionalFile[`P${pdoc.id}`] = [{ fromPid: pid, filename }];
|
|
|
|
|
else problemAdditionalFile[`P${pdoc.id}`].push({ fromPid: pid, filename });
|
|
|
|
|
content = content.replace(origialPath, `file://${filename}`);
|
|
|
|
|
}
|
|
|
|
|
const pid = await ProblemModel.add(domainId, `P${pdoc.id}`, pdoc.title, content, uidMap[pdoc.user_id] || 1);
|
|
|
|
|
pidMap[pdoc.id] = pid;
|
|
|
|
|
}
|
|
|
|
|
const tags = await query(`SELECT * FROM \`problem_tag_map\` WHERE \`problem_id\` = ${pdoc.id}`);
|
|
|
|
|
const tagList = [];
|
|
|
|
|
for (const tag of tags) tagList.push(tagMap[tag.tag_id]);
|
|
|
|
|
await ProblemModel.edit(domainId, pidMap[pdoc.id], {
|
|
|
|
|
nAccept: pdoc.ac_num || 0,
|
|
|
|
|
nSubmit: pdoc.submit_num || 0,
|
|
|
|
|
hidden: pdoc.is_public !== 1,
|
|
|
|
|
tag: tagList,
|
|
|
|
|
});
|
|
|
|
|
configMap[`P${pdoc.id}`] = `type: ${({ traditional: 'default', 'submit-answer': 'submit_answer' })[pdoc.type] || pdoc.type}
|
|
|
|
|
\ntime: ${pdoc.time_limit}ms\nmemory: ${pdoc.memory_limit}m${pdoc.file_io ? `\nfilename: ${pdoc.file_io_input_name.split('.')[0]}` : ''}`;
|
|
|
|
|
if (pdoc.additional_file_id) {
|
|
|
|
|
const additionalFile = await query(`SELECT * FROM \`file\` WHERE \`id\` = ${pdoc.additional_file_id}`);
|
|
|
|
|
if (additionalFile.length) {
|
|
|
|
|
const [afdoc] = additionalFile;
|
|
|
|
|
await ProblemModel.addAdditionalFile(domainId, pdoc.docId,
|
|
|
|
|
`additional_file_${pdoc.additional_file_id}.zip`, `${dataDir}/additional_file/${afdoc.md5}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
report({ message: 'problem finished' });
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
id: number; @TypeORM.PrimaryGeneratedColumn()
|
|
|
|
|
title: string; @TypeORM.Column({ nullable: true, type: "varchar", length: 80 })
|
|
|
|
|
subtitle: string; @TypeORM.Column({ nullable: true, type: "text" })
|
|
|
|
|
start_time: number; @TypeORM.Column({ nullable: true, type: "integer" })
|
|
|
|
|
end_time: number; @TypeORM.Column({ nullable: true, type: "integer" })
|
|
|
|
|
holder_id: number; @TypeORM.Column({ nullable: true, type: "integer" })
|
|
|
|
|
type: ContestType; // type: noi, ioi, acm
|
|
|
|
|
information: string; @TypeORM.Column({ nullable: true, type: "text" })
|
|
|
|
|
problems: string; @TypeORM.Column({ nullable: true, type: "text" })
|
|
|
|
|
admins: string; @TypeORM.Column({ nullable: true, type: "text" })
|
|
|
|
|
ranklist_id: number; @TypeORM.Column({ nullable: true, type: "integer" })
|
|
|
|
|
is_public: boolean; @TypeORM.Column({ nullable: true, type: "boolean" })
|
|
|
|
|
hide_statistics: boolean; @TypeORM.Column({ nullable: true, type: "boolean" })
|
|
|
|
|
holder?: User;
|
|
|
|
|
ranklist?: ContestRanklist;
|
|
|
|
|
*/
|
|
|
|
|
const ratedTids = (await query('SELECT `contest_id` FROM `rating_calculation`')).map(({ contest_id: id }) => id);
|
|
|
|
|
const tidMap: Record<string, string> = {};
|
|
|
|
|
const tdocs = await query('SELECT * FROM `contest`');
|
|
|
|
|
for (const tdoc of tdocs) {
|
|
|
|
|
const pdocs = tdoc.problems.split('|').map((i) => i.trim());
|
|
|
|
|
const pids = pdocs.map((i) => pidMap[i]).filter((i) => i);
|
|
|
|
|
const admin = uidMap[tdoc.holder_id] || uidMap[tdoc.admins.split('|')[0]];
|
|
|
|
|
const tid = await ContestModel.add(
|
|
|
|
|
domainId, tdoc.title, `${tdoc.subtitle ? `#### ${tdoc.subtitle}\n` : ''}${tdoc.information || 'No Description'}`,
|
|
|
|
|
admin, contentTypeMap[tdoc.type], new Date(tdoc.start_time * 1000), new Date(tdoc.end_time * 1000),
|
|
|
|
|
pids, ratedTids.includes(tdoc.id), { maintainer: tdoc.admins.split('|').map((i) => uidMap[i]) },
|
|
|
|
|
);
|
|
|
|
|
tidMap[tdoc.id] = tid.toHexString();
|
|
|
|
|
}
|
|
|
|
|
report({ message: 'contest finished' });
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
`id` int NOT NULL AUTO_INCREMENT,
|
|
|
|
|
`code` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`language` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
|
|
|
|
`status` statusMap
|
|
|
|
|
`task_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
|
|
|
|
`score` int NULL DEFAULT 0,
|
|
|
|
|
`total_time` int NULL DEFAULT 0,
|
|
|
|
|
`code_length` int NULL DEFAULT 0,
|
|
|
|
|
`pending` tinyint NULL DEFAULT 0,
|
|
|
|
|
`max_memory` int NULL DEFAULT 0,
|
|
|
|
|
`compilation` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`result` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`user_id` int NULL DEFAULT NULL,
|
|
|
|
|
`problem_id` int NULL DEFAULT NULL,
|
|
|
|
|
`submit_time` int NULL DEFAULT NULL,
|
|
|
|
|
|
|
|
|
|
* "type" indicate it's contest's submission(type = 1) or normal submission(type = 0)
|
|
|
|
|
* if it's contest's submission (type = 1), the type_info is contest_id
|
|
|
|
|
`type` int NULL DEFAULT NULL,
|
|
|
|
|
`type_info` int NULL DEFAULT NULL,
|
|
|
|
|
|
|
|
|
|
`is_public` tinyint NULL DEFAULT NULL,
|
|
|
|
|
*/
|
|
|
|
|
const [{ 'count(*)': rcount }] = await query('SELECT count(*) FROM `judge_state`');
|
|
|
|
|
const rpageCount = Math.ceil(Number(rcount) / step);
|
|
|
|
|
for (let pageId = 0; pageId < rpageCount; pageId++) {
|
|
|
|
|
const rdocs = await query(`SELECT * FROM \`judge_state\` LIMIT ${pageId * step}, ${step}`);
|
|
|
|
|
for (const rdoc of rdocs) {
|
|
|
|
|
const data: RecordDoc = {
|
|
|
|
|
status: statusMap[rdoc.status] || 0,
|
|
|
|
|
_id: Time.getObjectID(new Date(rdoc.submit_time * 1000), false),
|
|
|
|
|
uid: uidMap[rdoc.user_id] || 0,
|
|
|
|
|
code: rdoc.code,
|
|
|
|
|
lang: langMap[rdoc.language] || '',
|
|
|
|
|
pid: pidMap[rdoc.problem_id] || 0,
|
|
|
|
|
domainId,
|
|
|
|
|
score: rdoc.score || 0,
|
|
|
|
|
time: rdoc.total_time || 0,
|
|
|
|
|
memory: rdoc.max_memory || 0,
|
|
|
|
|
judgeTexts: [],
|
|
|
|
|
compilerTexts: [],
|
|
|
|
|
testCases: [],
|
|
|
|
|
judgeAt: new Date(),
|
|
|
|
|
rejudged: false,
|
|
|
|
|
judger: 1,
|
|
|
|
|
};
|
|
|
|
|
const judgeState = JSON.parse(rdoc.result);
|
|
|
|
|
if (judgeState) {
|
|
|
|
|
if (judgeState.compile?.message) data.compilerTexts.push(judgeState.compile.message.replace(/<.+?>/g, ''));
|
|
|
|
|
if (judgeState.judge) {
|
|
|
|
|
judgeState.judge.subtasks.forEach((subtask, index) => {
|
|
|
|
|
subtask.cases.forEach((curCase, caseIndex) => {
|
|
|
|
|
data.testCases.push({
|
|
|
|
|
subtaskId: index + 1,
|
|
|
|
|
id: caseIndex + 1,
|
|
|
|
|
score: Math.trunc((curCase.result?.scoringRate || 0) * 100),
|
|
|
|
|
time: curCase.result?.time || 0,
|
|
|
|
|
memory: curCase.result?.memory || 0,
|
|
|
|
|
message: curCase.result?.spjMessage || curCase.result?.systemMessage || curCase.result?.userError || '',
|
|
|
|
|
status: curCase.status === 2 ? TestcaseJudgeStatusMap[curCase.result.type] : TestcaseStatusMap[curCase.status],
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (rdoc.type) {
|
|
|
|
|
data.contest = new ObjectID(tidMap[rdoc.type_info]);
|
|
|
|
|
await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
|
|
|
|
|
}
|
|
|
|
|
await RecordModel.coll.insertOne(data);
|
|
|
|
|
await postJudge(data).catch((err) => report({ message: err.message }));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
report({ message: 'record finished' });
|
|
|
|
|
|
|
|
|
|
/* article
|
|
|
|
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
|
|
|
`title` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
|
|
|
|
`content` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
|
|
|
|
|
`user_id` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`problem_id` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`public_time` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`update_time` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`sort_time` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`comments_num` int(11) NOT NULL DEFAULT 0,
|
|
|
|
|
`allow_comment` tinyint(4) NOT NULL DEFAULT 1,
|
|
|
|
|
`is_notice` tinyint(4) NULL DEFAULT NULL,
|
|
|
|
|
*/
|
|
|
|
|
const ddocs = await query('SELECT * FROM `article`');
|
|
|
|
|
const didMap = {};
|
|
|
|
|
for (const ddoc of ddocs) {
|
|
|
|
|
const _id = Time.getObjectID(new Date(ddoc.public_time * 1000), false);
|
|
|
|
|
const data: Partial<DiscussionDoc> = {
|
|
|
|
|
_id,
|
|
|
|
|
docType: DocumentModel.TYPE_DISCUSSION,
|
|
|
|
|
docId: _id,
|
|
|
|
|
owner: uidMap[ddoc.user_id] || 0,
|
|
|
|
|
title: ddoc.title,
|
|
|
|
|
content: ddoc.content,
|
|
|
|
|
domainId,
|
|
|
|
|
updateAt: new Date(ddoc.update_time * 1000),
|
|
|
|
|
nReply: ddoc.comments_num,
|
|
|
|
|
views: 0,
|
|
|
|
|
lock: ddoc.allow_comment === 0,
|
|
|
|
|
pin: ddoc.is_notice === 1,
|
|
|
|
|
highlight: ddoc.is_notice === 1,
|
|
|
|
|
parentType: ddoc.problem_id ? DocumentModel.TYPE_PROBLEM : DocumentModel.TYPE_DISCUSSION_NODE,
|
|
|
|
|
parentId: pidMap[ddoc.problem_id] || 'Hydro',
|
|
|
|
|
ip: '127.0.0.1',
|
|
|
|
|
};
|
|
|
|
|
await DocumentModel.coll.insertOne(data);
|
|
|
|
|
didMap[ddoc.id] = data._id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* article_comment
|
|
|
|
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
|
|
|
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
|
|
|
|
|
`article_id` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`user_id` int(11) NULL DEFAULT NULL,
|
|
|
|
|
`public_time` int(11) NULL DEFAULT NULL,
|
|
|
|
|
*/
|
|
|
|
|
const drdocs = await query('SELECT * FROM `article_comment`');
|
|
|
|
|
for (const drdoc of drdocs) {
|
|
|
|
|
const _id = Time.getObjectID(new Date(drdoc.public_time * 1000), false);
|
|
|
|
|
const data: Partial<DiscussionReplyDoc> = {
|
|
|
|
|
_id,
|
|
|
|
|
domainId,
|
|
|
|
|
docId: _id,
|
|
|
|
|
docType: DocumentModel.TYPE_DISCUSSION_REPLY,
|
|
|
|
|
content: drdoc.content,
|
|
|
|
|
owner: uidMap[drdoc.user_id],
|
|
|
|
|
parentType: DocumentModel.TYPE_DISCUSSION,
|
|
|
|
|
parentId: didMap[drdoc.article_id],
|
|
|
|
|
ip: '127.0.0.1',
|
|
|
|
|
};
|
|
|
|
|
await DocumentModel.coll.insertOne(data);
|
|
|
|
|
}
|
|
|
|
|
report({ message: 'article finished' });
|
|
|
|
|
|
|
|
|
|
src.end();
|
|
|
|
|
|
|
|
|
|
if (!dataDir) return true;
|
|
|
|
|
if (dataDir.endsWith('/')) dataDir = dataDir.slice(0, -1);
|
|
|
|
|
const files = await fs.readdir(`${dataDir}/testdata/`, { withFileTypes: true });
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
if (!file.isDirectory()) continue;
|
|
|
|
|
const datas = await fs.readdir(`${dataDir}/testdata/${file.name}`, { withFileTypes: true });
|
|
|
|
|
const pdoc = await ProblemModel.get(domainId, `P${file.name}`, undefined, true);
|
|
|
|
|
if (!pdoc) continue;
|
|
|
|
|
report({ message: `Syncing testdata for ${file.name}` });
|
|
|
|
|
for (const data of datas) {
|
|
|
|
|
if (data.isDirectory()) continue;
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId, data.name, `${dataDir}/testdata/${file.name}/${data.name}`);
|
|
|
|
|
if (data.name.startsWith('spj_')) {
|
|
|
|
|
report({ message: `Syncing spj for ${file.name}` });
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId,
|
|
|
|
|
`spj.${langMap[data.name.split('spj_')[1].split('.')[0]]}`, `${dataDir}/testdata/${file.name}/${data.name}`);
|
|
|
|
|
pdoc.config += `\nchecker_type: syzoj\nchecker: spj.${langMap[data.name.split('spj_')[1].split('.')[0]]}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!(datas.find((i) => i.name === 'data.yml'))) {
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(configMap[`P${file.name}`]));
|
|
|
|
|
} else {
|
|
|
|
|
report({ message: `Transfering data.yml for ${file.name}` });
|
|
|
|
|
const config = yaml.load(configMap[`P${file.name}`]) as any;
|
|
|
|
|
const syzojConfig = yaml.load(fs.readFileSync(`${dataDir}/testdata/${file.name}/data.yml`, 'utf8').toString()) as any;
|
|
|
|
|
if (syzojConfig.specialJudge) {
|
|
|
|
|
report({ message: `Syncing spj config for ${file.name}` });
|
|
|
|
|
config.checker_type = 'syzoj';
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId,
|
|
|
|
|
`spj.${langMap[syzojConfig.specialJudge.language]}`, `${dataDir}/testdata/${file.name}/${syzojConfig.specialJudge.fileName}`);
|
|
|
|
|
config.checker = `spj.${langMap[syzojConfig.specialJudge.language]}`;
|
|
|
|
|
}
|
|
|
|
|
if (syzojConfig.subtasks) {
|
|
|
|
|
config.subtasks = syzojConfig.subtasks.map((subtask, index) => ({
|
|
|
|
|
score: subtask.score,
|
|
|
|
|
id: index + 1,
|
|
|
|
|
type: subtask.type,
|
|
|
|
|
cases: subtask.cases.map((caseItem) => ({
|
|
|
|
|
input: syzojConfig.inputFile.replace('#', caseItem),
|
|
|
|
|
output: syzojConfig.outputFile.replace('#', caseItem),
|
|
|
|
|
})),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
if (syzojConfig.extraSourceFiles?.length === 1) {
|
|
|
|
|
for (const { name: sourceName, dest } of syzojConfig.extraSourceFiles[0].files) {
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId, dest,
|
|
|
|
|
`${dataDir}/testdata/${file.name}/${sourceName}`);
|
|
|
|
|
}
|
|
|
|
|
config.user_extra_files = syzojConfig.extraSourceFiles[0].files.map((x) => x.dest);
|
|
|
|
|
} else if (syzojConfig.extraSourceFiles?.length > 1) {
|
|
|
|
|
report({ message: `Multiple extra source files are not supported for ${file.name}` });
|
|
|
|
|
}
|
|
|
|
|
if (config.type === 'submit_answer') {
|
|
|
|
|
config.subType = 'multi';
|
|
|
|
|
config.filename = syzojConfig.outputFile;
|
|
|
|
|
}
|
|
|
|
|
if (syzojConfig.interactor) {
|
|
|
|
|
report({ message: `Syncing interactor config for ${file.name}` });
|
|
|
|
|
config.type = 'interactive';
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId,
|
|
|
|
|
`spj.${langMap[syzojConfig.interactor.language]}`, `${dataDir}/testdata/${file.name}/${syzojConfig.interactor.fileName}`);
|
|
|
|
|
config.interactor = `spj.${langMap[syzojConfig.interactor.language]}`;
|
|
|
|
|
}
|
|
|
|
|
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(yaml.dump(config)));
|
|
|
|
|
}
|
|
|
|
|
if (problemAdditionalFile[`P${file.name}`]) {
|
|
|
|
|
report({ message: `Syncing additional_file for ${file.name}` });
|
|
|
|
|
for (const data of problemAdditionalFile[`P${file.name}`]) {
|
|
|
|
|
if (!fs.existsSync(`${dataDir}/testdata/${data.fromPid}/${decodeURIComponent(data.filename)}`)) continue;
|
|
|
|
|
await ProblemModel.addAdditionalFile(domainId, pdoc.docId, data.filename,
|
|
|
|
|
`${dataDir}/testdata/${data.fromPid}/${decodeURIComponent(data.filename)}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|