mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
onsite-toolkit: integrate with external resolver
This commit is contained in:
8 files changed
+197
-693
No files matched your search
+188
-231
@@ -3,11 +3,10 @@ import { LRUCache } from 'lru-cache';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
_, avatar, BadRequestError, ContestModel, ContestNotEndedError, Context, db, findFileSync,
|
||||
ForbiddenError, fs, ObjectId, parseTimeMS, PERM, ProblemConfig, ProblemModel,
|
||||
ForbiddenError, fs, Handler, InvalidTokenError, ObjectId, param, parseTimeMS, PERM, ProblemConfig, ProblemModel,
|
||||
randomstring, Schema, SettingModel, STATUS, STATUS_SHORT_TEXTS, STATUS_TEXTS,
|
||||
SystemModel, Time, Types, UserModel, Zip,
|
||||
SystemModel, TokenModel, Types, UserModel, Zip,
|
||||
} from 'hydrooj';
|
||||
import { ResolverInput } from './interface';
|
||||
|
||||
interface IpLoginInfo {
|
||||
_id: string;
|
||||
@@ -89,245 +88,203 @@ export function apply(ctx: Context, config: ReturnType<typeof Config>) {
|
||||
if (['domain', 'account'].includes(that.args.category)) throw new ForbiddenError('Not available');
|
||||
});
|
||||
|
||||
ctx.inject(['scoreboard'], ({ scoreboard }) => {
|
||||
scoreboard.addView('resolver-tiny', 'Resolver(Tiny)', { tdoc: 'tdoc' }, {
|
||||
async display({ tdoc }) {
|
||||
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
const teams = await ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc.docId }).toArray();
|
||||
const udict = await UserModel.getList(tdoc.domainId, teams.map((i) => i.uid));
|
||||
const teamIds: Record<number, number> = {};
|
||||
for (let i = 1; i <= teams.length; i++) teamIds[teams[i - 1].uid] = i;
|
||||
const time = (t: ObjectId) => Math.floor((t.getTimestamp().getTime() - tdoc.beginAt.getTime()) / Time.second);
|
||||
const pid = (i: number) => String.fromCharCode(65 + i);
|
||||
const unknownSchool = this.translate('Unknown School');
|
||||
const submissions = teams.flatMap((i) => {
|
||||
if (!i.journal) return [];
|
||||
return i.journal.filter((s) => tdoc.pids.includes(s.pid)).map((s) => ({ ...s, uid: i.uid }));
|
||||
});
|
||||
this.response.body = {
|
||||
payload: {
|
||||
name: tdoc.title,
|
||||
duration: Math.floor((new Date(tdoc.endAt).getTime() - new Date(tdoc.beginAt).getTime()) / 1000),
|
||||
frozen: Math.floor((new Date(tdoc.lockAt).getTime() - new Date(tdoc.beginAt).getTime()) / 1000),
|
||||
problems: tdoc.pids.map((i, n) => ({ name: pid(n), id: i.toString() })),
|
||||
teams: teams.map((t) => ({
|
||||
id: t.uid.toString(),
|
||||
name: udict[t.uid].displayName || udict[t.uid].uname,
|
||||
avatar: avatar(udict[t.uid].avatar),
|
||||
institution: udict[t.uid].school || unknownSchool,
|
||||
exclude: t.unrank,
|
||||
})),
|
||||
submissions: submissions.map((i) => ({
|
||||
team: udict[i.uid]?.seat || i.uid.toString(),
|
||||
problem: i.pid.toString(),
|
||||
verdict: STATUS_SHORT_TEXTS[i.status],
|
||||
time: time(i.rid),
|
||||
})),
|
||||
} as ResolverInput,
|
||||
};
|
||||
this.response.template = 'resolver.html';
|
||||
},
|
||||
supportedRules: ['acm'],
|
||||
async function generateCdpZip(tdoc) {
|
||||
const serverUrl = SystemModel.get('server.url');
|
||||
try {
|
||||
new URL(serverUrl);
|
||||
} catch (e) {
|
||||
throw new BadRequestError('Server URL not set');
|
||||
}
|
||||
let token = 0;
|
||||
const getFeed = (type: string, data: any) => ({
|
||||
type, id: data.id, data, token: `t${token++}`,
|
||||
});
|
||||
const [pdict, tsdocs] = await Promise.all([
|
||||
ProblemModel.getList(tdoc.domainId, tdoc.pids, true, false, ProblemModel.PROJECTION_LIST.concat('config'), true),
|
||||
ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc._id }).toArray(),
|
||||
]);
|
||||
const udict = await UserModel.getList(tdoc.domainId, tsdocs.map((i) => i.uid));
|
||||
const teams = tsdocs.map((i) => {
|
||||
const udoc = udict[i.uid];
|
||||
return {
|
||||
team_id: udoc.seat || `team-${udoc._id}`,
|
||||
name: udoc.uname,
|
||||
displayName: (i.unrank ? '⭐' : '') + (udoc.displayName || udoc.uname),
|
||||
organization: udoc.school || udoc.uname,
|
||||
avatar: avatar(udoc.avatar),
|
||||
group: [
|
||||
...(udoc.group.filter((g) => g !== `${udoc._id}`)),
|
||||
i.unrank ? 'observers' : 'participants',
|
||||
],
|
||||
};
|
||||
});
|
||||
const relatedGroups = Array.from(new Set(teams.flatMap((i) => i.group)));
|
||||
const groupId = {};
|
||||
let gid = 1;
|
||||
for (const i of relatedGroups) groupId[i] = `group-${gid++}`;
|
||||
const organizations = Array.from(new Set(teams.flatMap((i) => i.organization)));
|
||||
const orgId = {};
|
||||
let oid = 1;
|
||||
for (const i of organizations) orgId[i] = `org-${oid++}`;
|
||||
const duration = moment(tdoc.endAt).diff(tdoc.beginAt, 'seconds');
|
||||
const lockDuration = tdoc.lockAt
|
||||
? moment(tdoc.endAt).diff(tdoc.lockAt, 'seconds')
|
||||
: tdoc.rule === 'oi' ? duration : null;
|
||||
const eventfeed: Record<string, any>[] = [
|
||||
getFeed('contest', {
|
||||
id: tdoc._id.toHexString(),
|
||||
name: tdoc.title,
|
||||
formal_name: tdoc.title,
|
||||
start_time: moment(tdoc.beginAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
duration: moment().startOf('day').seconds(duration).format('HH:mm:ss.SSS'),
|
||||
...lockDuration && {
|
||||
scoreboard_freeze_duration: moment().startOf('day').seconds(lockDuration).format('HH:mm:ss.SSS'),
|
||||
},
|
||||
penalty_time: 20,
|
||||
}),
|
||||
...Object.keys(STATUS_SHORT_TEXTS).map((i) => getFeed('judgement-types', {
|
||||
id: STATUS_SHORT_TEXTS[i],
|
||||
name: STATUS_TEXTS[i],
|
||||
penalty: ![STATUS.STATUS_ACCEPTED, STATUS.STATUS_COMPILE_ERROR, STATUS.STATUS_SYSTEM_ERROR].includes(+i),
|
||||
solved: +i === STATUS.STATUS_ACCEPTED,
|
||||
})),
|
||||
getFeed('languages', { id: 'c', name: 'C', entry_point_required: false, extensions: ['c'] }),
|
||||
getFeed('languages', { id: 'cpp', name: 'C++', entry_point_required: false, extensions: ['cpp', 'cc', 'cxx', 'c++'] }),
|
||||
getFeed('languages', { id: 'java', name: 'Java', entry_point_required: true, extensions: ['java'] }),
|
||||
getFeed('languages', { id: 'python3', name: 'Python 3', entry_point_required: false, entry_point_name: 'Main file', extensions: ['py', 'py3'] }),
|
||||
getFeed('languages', { id: 'kotlin', name: 'Kotlin', entry_point_required: true, extensions: ['kt'] }),
|
||||
getFeed('languages', { id: 'rust', name: 'Rust', entry_point_required: true, extensions: ['rs'] }),
|
||||
getFeed('languages', { id: 'go', name: 'Go', entry_point_required: true, extensions: ['go'] }),
|
||||
getFeed('groups', { id: 'participants', name: '正式队伍' }),
|
||||
getFeed('groups', { id: 'observers', name: '打星队伍' }),
|
||||
...relatedGroups.map((i) => getFeed('groups', { id: groupId[i], name: i })),
|
||||
...organizations.map((i) => getFeed('organizations', {
|
||||
id: orgId[i], name: i, formal_name: i,
|
||||
logo: [{ href: `contest/${tdoc._id}/organizations/${orgId[i]}/logo`, filename: 'logo.png', mime: 'image/png', width: 64, height: 64 }],
|
||||
})),
|
||||
...teams.map((i) => getFeed('teams', {
|
||||
id: i.team_id, label: i.team_id, name: i.name,
|
||||
display_name: i.displayName || i.name,
|
||||
group_ids: i.group.map((j) => groupId[j]),
|
||||
organization_id: orgId[i.organization],
|
||||
photo: [{ href: `contest/${tdoc._id}/teams/${i.team_id}/photo`, filename: 'photo.png', mime: 'image/png', width: 1920, height: 1080 }],
|
||||
logo: [{ href: new URL(i.avatar, serverUrl).toString(), filename: 'logo.webp', mime: 'image/webp', width: 128, height: 128 }],
|
||||
})),
|
||||
...tdoc.pids.map((i, idx) => getFeed('problems', {
|
||||
id: `${i}`, label: String.fromCharCode(65 + idx), name: pdict[i].title, ordinal: idx,
|
||||
color: (typeof (tdoc.balloon?.[idx]) === 'object' ? tdoc.balloon[idx].name : tdoc.balloon?.[idx]) || 'white',
|
||||
rgb: (typeof (tdoc.balloon?.[idx]) === 'object' ? tdoc.balloon[idx].color : null) || '#ffffff',
|
||||
time_limit: Math.floor(parseTimeMS((pdict[i].config as ProblemConfig).timeMax) / 100) / 10,
|
||||
test_data_count: 20,
|
||||
})),
|
||||
];
|
||||
let cntJudge = 0;
|
||||
const submissions = tsdocs.flatMap((i) => {
|
||||
if (!i.journal) return [];
|
||||
const journal = i.journal.filter((s) => tdoc.pids.includes(s.pid));
|
||||
const result: any[] = [];
|
||||
for (const s of journal) {
|
||||
const submitTime = moment(s.rid.getTimestamp());
|
||||
const submitDelta = submitTime.diff(tdoc.beginAt, 'seconds');
|
||||
const submitAt = moment().startOf('day').seconds(submitDelta).format('HH:mm:ss.SSS');
|
||||
const judgeTime = submitTime.add(1, 'seconds');
|
||||
const judgeDelta = judgeTime.diff(tdoc.beginAt, 'seconds');
|
||||
const judgeAt = moment().startOf('day').seconds(judgeDelta).format('HH:mm:ss.SSS');
|
||||
result.push(getFeed('submissions', {
|
||||
id: s.rid, team_id: udict[i.uid]?.seat || `team-${i.uid}`,
|
||||
problem_id: `${s.pid}`, language_id: s.lang?.split('.')[0] || 'cpp',
|
||||
files: [], contest_time: submitAt,
|
||||
time: submitTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
}));
|
||||
result.push(getFeed('judgements', {
|
||||
id: `${cntJudge}`, submission_id: s.rid,
|
||||
judgement_type_id: STATUS_SHORT_TEXTS[s.status], max_run_time: 0.1,
|
||||
start_contest_time: submitAt, end_contest_time: judgeAt,
|
||||
start_time: submitTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
end_time: judgeTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
...s.score != null && { score: s.score },
|
||||
}));
|
||||
cntJudge++;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
const endState = [
|
||||
getFeed('state', {
|
||||
started: moment(tdoc.beginAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
ended: moment(tdoc.endAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
...tdoc.lockAt && { frozen: moment(tdoc.lockAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00') },
|
||||
finalized: moment().format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
}),
|
||||
];
|
||||
const zip = new Zip.ZipWriter(new Zip.BlobWriter());
|
||||
await Promise.all([
|
||||
zip.add('event-feed.ndjson', new Zip.TextReader(eventfeed.concat(submissions).concat(endState).map((i) => JSON.stringify(i)).join('\n'))),
|
||||
zip.add('contest/logo.png', new Zip.BlobReader(new Blob([fs.readFileSync(findFileSync('@hydrooj/onsite-toolkit/public/logo.png'))]))),
|
||||
zip.add('teams/', null, { directory: true }),
|
||||
zip.add('organizations/', null, { directory: true }),
|
||||
]);
|
||||
await Promise.all(teams.map(async (i) => {
|
||||
await zip.add(`teams/${i.team_id}/`, null, { directory: true });
|
||||
await zip.add(`teams/${i.team_id}/photo.url`, new Zip.TextReader(`URL=${i.avatar}`));
|
||||
}));
|
||||
await Promise.all(organizations.map(async (i) => {
|
||||
const avatarSrc = teams.find((j) => j.organization === i)?.avatar;
|
||||
if (!avatarSrc) return;
|
||||
await zip.add(`organizations/${orgId[i]}/`, null, { directory: true });
|
||||
await zip.add(`organizations/${orgId[i]}/photo.url`, new Zip.TextReader(`URL=${avatar(avatarSrc)}`));
|
||||
}));
|
||||
return zip.close();
|
||||
}
|
||||
|
||||
class ContestResolverCdpHandler extends Handler {
|
||||
allowCors = true;
|
||||
@param('tid', Types.ObjectId)
|
||||
@param('token', Types.String)
|
||||
async get(domainId: string, tid: ObjectId, token: string) {
|
||||
const tokenDoc = await TokenModel.get(token, TokenModel.TYPE_EXPORT);
|
||||
if (!tokenDoc || tokenDoc.domainId !== domainId || tokenDoc.contestId?.toHexString() !== tid.toHexString()) {
|
||||
throw new InvalidTokenError(token);
|
||||
}
|
||||
const tdoc = await ContestModel.get(domainId, tid);
|
||||
if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError();
|
||||
this.response.addHeader('Access-Control-Allow-Origin', '*');
|
||||
this.binary(await generateCdpZip(tdoc), `contest-${tid}-cdp.zip`);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Route('contest_resolver_cdp', '/contest/:tid/resolver-cdp/:token', ContestResolverCdpHandler);
|
||||
|
||||
ctx.inject(['scoreboard'], ({ scoreboard }) => {
|
||||
scoreboard.addView('cdp', 'CDP', { tdoc: 'tdoc' }, {
|
||||
async display({ tdoc }) {
|
||||
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError();
|
||||
this.binary(await generateCdpZip(tdoc), `contest-${tdoc._id}-cdp.zip`);
|
||||
},
|
||||
supportedRules: ['acm', 'oi'],
|
||||
});
|
||||
|
||||
scoreboard.addView('resolver', 'Resolver', { tdoc: 'tdoc' }, {
|
||||
async display({ tdoc }) {
|
||||
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError();
|
||||
const serverUrl = SystemModel.get('server.url');
|
||||
try {
|
||||
new URL(SystemModel.get('server.url')); // eslint-disable-line no-new
|
||||
new URL(serverUrl);
|
||||
} catch (e) {
|
||||
throw new BadRequestError('Server URL not set');
|
||||
}
|
||||
let token = 0;
|
||||
const getFeed = (type: string, data: any) => ({
|
||||
type, id: data.id, data, token: `t${token++}`,
|
||||
});
|
||||
const [pdict, tsdocs] = await Promise.all([
|
||||
ProblemModel.getList(tdoc.domainId, tdoc.pids, true, false, ProblemModel.PROJECTION_LIST.concat('config'), true),
|
||||
ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc._id }).toArray(),
|
||||
]);
|
||||
const udict = await UserModel.getList(tdoc.domainId, tsdocs.map((i) => i.uid));
|
||||
const teams = tsdocs.map((i) => {
|
||||
const udoc = udict[i.uid];
|
||||
return {
|
||||
team_id: udoc.seat || `team-${udoc._id}`,
|
||||
name: udoc.uname,
|
||||
displayName: (i.unrank ? '⭐' : '') + (udoc.displayName || udoc.uname),
|
||||
organization: udoc.school || udoc.uname,
|
||||
avatar: avatar(udoc.avatar),
|
||||
group: [
|
||||
...(udoc.group.filter((g) => g !== `${udoc._id}`)),
|
||||
i.unrank ? 'observers' : 'participants',
|
||||
],
|
||||
};
|
||||
});
|
||||
const relatedGroups = Array.from(new Set(teams.flatMap((i) => i.group)));
|
||||
const groupId = {};
|
||||
let gid = 1;
|
||||
for (const i of relatedGroups) groupId[i] = `group-${gid++}`;
|
||||
const organizations = Array.from(new Set(teams.flatMap((i) => i.organization)));
|
||||
const orgId = {};
|
||||
let oid = 1;
|
||||
for (const i of organizations) orgId[i] = `org-${oid++}`;
|
||||
const duration = moment(tdoc.endAt).diff(tdoc.beginAt, 'seconds');
|
||||
const lockDuration = tdoc.lockAt ? moment(tdoc.endAt).diff(tdoc.lockAt, 'seconds') : null;
|
||||
const eventfeed: Record<string, any>[] = [
|
||||
getFeed('contest', {
|
||||
id: tdoc._id.toHexString(),
|
||||
name: tdoc.title,
|
||||
formal_name: tdoc.title,
|
||||
start_time: moment(tdoc.beginAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
// 1:00:00.000
|
||||
duration: moment().startOf('day').seconds(duration).format('HH:mm:ss.SSS'),
|
||||
...lockDuration && {
|
||||
scoreboard_freeze_duration: moment().startOf('day').seconds(lockDuration).format('HH:mm:ss.SSS'),
|
||||
},
|
||||
penalty_time: 20,
|
||||
}),
|
||||
...Object.keys(STATUS_SHORT_TEXTS).map((i) => getFeed('judgement-types', {
|
||||
id: STATUS_SHORT_TEXTS[i],
|
||||
name: STATUS_TEXTS[i],
|
||||
penalty: ![STATUS.STATUS_ACCEPTED, STATUS.STATUS_COMPILE_ERROR, STATUS.STATUS_SYSTEM_ERROR].includes(+i),
|
||||
solved: +i === STATUS.STATUS_ACCEPTED,
|
||||
})),
|
||||
getFeed('languages', {
|
||||
id: 'c', name: 'C', entry_point_required: false, extensions: ['c'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'cpp', name: 'C++', entry_point_required: false, extensions: ['cpp', 'cc', 'cxx', 'c++'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'java', name: 'Java', entry_point_required: true, extensions: ['java'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'python3', name: 'Python 3', entry_point_required: false, entry_point_name: 'Main file', extensions: ['py', 'py3'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'kotlin', name: 'Kotlin', entry_point_required: true, extensions: ['kt'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'rust', name: 'Rust', entry_point_required: true, extensions: ['rs'],
|
||||
}),
|
||||
getFeed('languages', {
|
||||
id: 'go', name: 'Go', entry_point_required: true, extensions: ['go'],
|
||||
}),
|
||||
getFeed('groups', { id: 'participants', name: '正式队伍' }),
|
||||
getFeed('groups', { id: 'observers', name: '打星队伍' }),
|
||||
...relatedGroups.map((i) => getFeed('groups', {
|
||||
id: groupId[i],
|
||||
name: i,
|
||||
})),
|
||||
...organizations.map((i) => getFeed('organizations', {
|
||||
id: orgId[i],
|
||||
name: i,
|
||||
formal_name: i,
|
||||
logo: [{
|
||||
href: `contest/${tdoc._id}/organizations/${orgId[i]}/logo`,
|
||||
filename: 'logo.png',
|
||||
mime: 'image/png',
|
||||
width: 64,
|
||||
height: 64,
|
||||
}],
|
||||
})),
|
||||
...teams.map((i) => getFeed('teams', {
|
||||
id: i.team_id,
|
||||
label: i.team_id,
|
||||
name: i.name,
|
||||
display_name: i.displayName || i.name,
|
||||
group_ids: i.group.map((j) => groupId[j]),
|
||||
organization_id: orgId[i.organization],
|
||||
photo: [{
|
||||
href: `contest/${tdoc._id}/teams/${i.team_id}/photo`,
|
||||
filename: 'photo.png',
|
||||
mime: 'image/png',
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
}],
|
||||
logo: [{
|
||||
href: new URL(i.avatar, SystemModel.get('server.url')).toString(),
|
||||
filename: 'logo.webp',
|
||||
mime: 'image/webp',
|
||||
width: 128,
|
||||
height: 128,
|
||||
}],
|
||||
})),
|
||||
...tdoc.pids.map((i, idx) => getFeed('problems', {
|
||||
id: `${i}`,
|
||||
label: String.fromCharCode(65 + idx),
|
||||
name: pdict[i].title,
|
||||
ordinal: idx,
|
||||
color: (typeof (tdoc.balloon?.[idx]) === 'object' ? tdoc.balloon[idx].name : tdoc.balloon?.[idx]) || 'white',
|
||||
rgb: (typeof (tdoc.balloon?.[idx]) === 'object' ? tdoc.balloon[idx].color : null) || '#ffffff',
|
||||
time_limit: Math.floor(parseTimeMS((pdict[i].config as ProblemConfig).timeMax) / 100) / 10,
|
||||
test_data_count: 20,
|
||||
})),
|
||||
];
|
||||
let cntJudge = 0;
|
||||
const submissions = tsdocs.flatMap((i) => {
|
||||
if (!i.journal) return [];
|
||||
const journal = i.journal.filter((s) => tdoc.pids.includes(s.pid));
|
||||
const result: any[] = [];
|
||||
for (const s of journal) {
|
||||
const submitTime = moment(s.rid.getTimestamp());
|
||||
const submitDelta = submitTime.diff(tdoc.beginAt, 'seconds');
|
||||
const submitAt = moment().startOf('day').seconds(submitDelta).format('HH:mm:ss.SSS');
|
||||
const judgeTime = submitTime.add(1, 'seconds');
|
||||
const judgeDelta = judgeTime.diff(tdoc.beginAt, 'seconds');
|
||||
const judgeAt = moment().startOf('day').seconds(judgeDelta).format('HH:mm:ss.SSS');
|
||||
result.push(getFeed('submissions', {
|
||||
id: s.rid,
|
||||
team_id: udict[i.uid]?.seat || `team-${i.uid}`,
|
||||
problem_id: `${s.pid}`,
|
||||
language_id: s.lang?.split('.')[0] || 'cpp',
|
||||
files: [],
|
||||
contest_time: submitAt,
|
||||
time: submitTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
}));
|
||||
result.push(getFeed('judgements', {
|
||||
id: `${cntJudge}`,
|
||||
submission_id: s.rid,
|
||||
judgement_type_id: STATUS_SHORT_TEXTS[s.status],
|
||||
max_run_time: 0.1,
|
||||
start_contest_time: submitAt,
|
||||
end_contest_time: judgeAt,
|
||||
start_time: submitTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
end_time: judgeTime.format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
}));
|
||||
cntJudge++;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
const endState = [
|
||||
getFeed('state', {
|
||||
started: moment(tdoc.beginAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
ended: moment(tdoc.endAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
...tdoc.lockAt && {
|
||||
frozen: moment(tdoc.lockAt).format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
},
|
||||
finalized: moment().format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||
}),
|
||||
];
|
||||
const zip = new Zip.ZipWriter(new Zip.BlobWriter());
|
||||
await Promise.all([
|
||||
zip.add('event-feed.ndjson', new Zip.TextReader(eventfeed.concat(submissions).concat(endState).map((i) => JSON.stringify(i)).join('\n'))),
|
||||
zip.add('contest/logo.png', new Zip.BlobReader(new Blob([fs.readFileSync(findFileSync('@hydrooj/onsite-toolkit/public/logo.png'))]))),
|
||||
zip.add('teams/', null, { directory: true }),
|
||||
zip.add('organizations/', null, { directory: true }),
|
||||
]);
|
||||
await Promise.all(teams.map(async (i) => {
|
||||
await zip.add(`teams/${i.team_id}/`, null, { directory: true });
|
||||
await zip.add(`teams/${i.team_id}/photo.url`, new Zip.TextReader(`URL=${i.avatar}`));
|
||||
}));
|
||||
await Promise.all(organizations.map(async (i) => {
|
||||
const avatarSrc = teams.find((j) => j.organization === i)?.avatar;
|
||||
if (!avatarSrc) return;
|
||||
await zip.add(`organizations/${orgId[i]}/`, null, { directory: true });
|
||||
await zip.add(`organizations/${orgId[i]}/photo.url`, new Zip.TextReader(`URL=${avatar(avatarSrc)}`));
|
||||
}));
|
||||
this.binary(await zip.close(), `contest-${tdoc._id}-cdp.zip`);
|
||||
const [tokenId] = await TokenModel.add(
|
||||
TokenModel.TYPE_EXPORT, 600,
|
||||
{ domainId: tdoc.domainId, contestId: tdoc._id },
|
||||
);
|
||||
const source = new URL(`/d/${tdoc.domainId}/contest/${tdoc._id}/resolver-cdp/${tokenId}`, serverUrl).toString();
|
||||
const target = new URL('https://resolver.hydrooj.com');
|
||||
target.searchParams.set('source', source);
|
||||
target.searchParams.set('mode', tdoc.rule === 'oi' ? 'oi' : 'acm');
|
||||
this.response.redirect = target.toString();
|
||||
},
|
||||
supportedRules: ['acm'],
|
||||
supportedRules: ['acm', 'oi'],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user