core: contest: scoreboard-view: add advanced checker

This commit is contained in:
undefined committed 2026-07-19 19:44:23 +00:00
1 parent aba8d9c755
commit 06640c90fb
2 files changed
+24 -9

No files matched your search

+10 -6
View File
@@ -825,6 +825,7 @@ export interface ScoreboardView<T extends { [key: string]: keyof BuiltinInput |
cacheTime?: number; // in seconds
args: T;
display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>;
checker?: (this: ContestScoreboardHandler) => boolean;
}
export class ContestScoreboardHandler extends ContestDetailBaseHandler {
@@ -884,16 +885,17 @@ class ScoreboardService extends Service {
addView<T extends { [key: string]: keyof BuiltinInput | AnyFunction | Type<any> }>(
id: string, name: string, args: T,
{ display, supportedRules, cacheTime }: {
{ display, supportedRules, cacheTime, checker }: {
display: (this: ContestScoreboardHandler, args: ParseArgs<T>) => Promise<void>;
supportedRules: string[];
cacheTime?: number;
checker?: (this: ContestScoreboardHandler) => boolean;
},
) {
if (this.views[id]) throw new Error(`View ${id} already exists`);
this.ctx.effect(() => {
this.views[id] = {
id, name, args, display, supportedRules, cacheTime,
id, name, args, display, supportedRules, cacheTime, checker,
};
return () => {
delete this.views[id];
@@ -901,9 +903,11 @@ class ScoreboardService extends Service {
});
}
getAvailableViews(rule: string) {
return Object.fromEntries(Object.values(this.views).filter((i) => i.supportedRules.includes(rule) || i.supportedRules.includes('*'))
.map((i) => [i.id, i.name]));
getAvailableViews(rule: string, handler: ContestScoreboardHandler) {
return Object.fromEntries(Object.values(this.views).filter((i) => (
(i.supportedRules.includes(rule) || i.supportedRules.includes('*'))
&& (!i.checker || i.checker.call(handler))
)).map((i) => [i.id, i.name]));
}
getView(id: string) {
@@ -963,7 +967,7 @@ export async function apply(ctx: Context) {
const page_name = tdoc.rule === 'homework'
? 'homework_scoreboard'
: 'contest_scoreboard';
const availableViews = scoreboard.getAvailableViews(tdoc.rule);
const availableViews = scoreboard.getAvailableViews(tdoc.rule, this);
this.response.body = {
tdoc: this.tdoc, tsdoc: this.tsdocAsPublic(), rows, udict, pdict, page_name, groups, availableViews,
};
+14 -3
View File
@@ -144,6 +144,7 @@ export function apply(ctx: Context, config: ReturnType<typeof Config>) {
...lockDuration && {
scoreboard_freeze_duration: moment().startOf('day').seconds(lockDuration).format('HH:mm:ss.SSS'),
},
scoreboard_type: tdoc.rule === 'acm' ? 'pass-fail' : 'score',
penalty_time: 20,
}),
...Object.keys(STATUS_SHORT_TEXTS).map((i) => getFeed('judgement-types', {
@@ -265,7 +266,12 @@ export function apply(ctx: Context, config: ReturnType<typeof Config>) {
if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError();
this.binary(await generateCdpZip(tdoc), `contest-${tdoc._id}-cdp.zip`);
},
supportedRules: ['acm', 'oi'],
checker() {
return ContestModel.isDone(this.tdoc)
&& !!this.tdoc.lockAt
&& (this.user.own(this.tdoc) || this.user.hasPerm(PERM.PERM_EDIT_CONTEST));
},
supportedRules: ['acm', 'oi', 'ioi'],
});
scoreboard.addView('resolver', 'Resolver', { tdoc: 'tdoc' }, {
@@ -279,10 +285,15 @@ export function apply(ctx: Context, config: ReturnType<typeof Config>) {
const source = new URL(`/d/${tdoc.domainId}/contest/${tdoc._id}/resolver-cdp/${tokenId}`, SystemModel.get('server.url')).toString();
const target = new URL('https://resolver.hydrooj.com');
target.searchParams.set('source', source);
target.searchParams.set('mode', tdoc.rule === 'oi' ? 'oi' : 'acm');
target.searchParams.set('mode', tdoc.rule === 'acm' ? 'acm' : 'oi');
this.response.redirect = target.toString();
},
supportedRules: ['acm', 'oi'],
checker() {
return ContestModel.isDone(this.tdoc)
&& !!this.tdoc.lockAt
&& (this.user.own(this.tdoc) || this.user.hasPerm(PERM.PERM_EDIT_CONTEST));
},
supportedRules: ['acm', 'oi', 'ioi'],
});
});