mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
onsite-toolkit: support export cdp package (#906)
Co-authored-by: Heartfirey <yanweiqi1754989931@live.com> Co-authored-by: cubercsl <2014cais01@gmail.com>
This commit is contained in:
2 files changed
+190
-2
No files matched your search
@@ -1,6 +1,9 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
avatar, ContestModel, Context, db, ForbiddenError,
|
AdmZip, avatar, ContestModel, ContestNotEndedError, Context, db, findFileSync, ForbiddenError,
|
||||||
ObjectId, PERM, STATUS, Time, UserModel,
|
fs,
|
||||||
|
ObjectId, parseTimeMS, PERM, ProblemConfig, ProblemModel, STATUS, STATUS_SHORT_TEXTS, STATUS_TEXTS, Time, UserModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { ResolverInput } from './interface';
|
import { ResolverInput } from './interface';
|
||||||
|
|
||||||
@@ -75,5 +78,190 @@ export function apply(ctx: Context) {
|
|||||||
},
|
},
|
||||||
supportedRules: ['acm'],
|
supportedRules: ['acm'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
scoreboard.addView('cdp', 'CDP', { tdoc: 'tdoc' }, {
|
||||||
|
async display({ tdoc }) {
|
||||||
|
if (ContestModel.isLocked(tdoc) && !this.user.own(tdoc)) {
|
||||||
|
this.checkPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||||
|
}
|
||||||
|
if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError();
|
||||||
|
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, 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: `team-${udoc._id}`,
|
||||||
|
name: udoc.uname,
|
||||||
|
displayName: udoc.displayName,
|
||||||
|
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 = 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.lockAt).diff(tdoc.beginAt, '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,
|
||||||
|
}],
|
||||||
|
})),
|
||||||
|
...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: (parseTimeMS((pdict[i].config as ProblemConfig).timeMax) / 1000).toFixed(1),
|
||||||
|
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: `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 AdmZip();
|
||||||
|
zip.addFile('event-feed.ndjson', Buffer.from(eventfeed.concat(submissions).concat(endState).map((i) => JSON.stringify(i)).join('\n')));
|
||||||
|
zip.addFile('contest/logo.png', fs.readFileSync(findFileSync('@hydrooj/onsite-toolkit/public/logo.png')));
|
||||||
|
for (const i of ['teams', 'organizations']) {
|
||||||
|
zip.addFile(`${i}/`, Buffer.alloc(0));
|
||||||
|
}
|
||||||
|
for (const i of teams) {
|
||||||
|
zip.addFile(`teams/${i.team_id}/photo.download.txt`, Buffer.from(i.avatar));
|
||||||
|
}
|
||||||
|
for (const i of organizations) {
|
||||||
|
zip.addFile(`organizations/${orgId[i]}/photo.download.txt`, Buffer.from(avatar(teams.find((j) => j.organization === i)?.avatar || 'no photo, find and download it yourself')));
|
||||||
|
}
|
||||||
|
this.binary(zip.toBuffer(), `contest-${tdoc._id}-cdp.zip`);
|
||||||
|
},
|
||||||
|
supportedRules: ['acm'],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Reference in new issue
Block a user