core: allow user to manually end contest early (#1147)

This commit is contained in:
Baoshuo authored and GitHub committed 2026-04-28 04:26:02 +08:00
1 parent d8a5716a22
commit 125c4b2388
9 files changed
+99 -40

No files matched your search

+38 -6
View File
@@ -93,14 +93,17 @@ export class ContestDetailBaseHandler extends Handler {
}
}
if (this.tdoc.duration && this.tsdoc?.startAt) {
const endAt = moment(this.tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate();
this.tsdoc.endAt = endAt < this.tdoc.endAt ? endAt : this.tdoc.endAt;
this.tsdoc.endAt = moment.min([
moment(this.tsdoc.startAt).add(this.tdoc.duration, 'hours'),
moment(this.tdoc.endAt),
...(this.tsdoc.endAt ? [moment(this.tsdoc.endAt)] : []),
]).toDate();
}
}
tsdocAsPublic() {
if (!this.tsdoc) return null;
return pick(this.tsdoc, ['attend', 'subscribe', 'startAt', ...(this.tdoc.duration ? ['endAt'] : [])]);
return pick(this.tsdoc, ['attend', 'subscribe', 'startAt', ...(this.tdoc.duration || this.tsdoc.endAt ? ['endAt'] : [])]);
}
@param('tid', Types.ObjectId, true)
@@ -173,7 +176,7 @@ export class ContestDetailHandler extends ContestDetailBaseHandler {
@param('code', Types.String, true)
async postAttend(domainId: string, tid: ObjectId, code = '') {
this.checkPerm(PERM.PERM_ATTEND_CONTEST);
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(tid);
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(domainId, tid);
if (this.tdoc._code && code !== this.tdoc._code) throw new InvalidTokenError('Contest Invitation', code);
await contest.attend(domainId, tid, this.user._id, { subscribe: 1 });
this.back();
@@ -186,6 +189,15 @@ export class ContestDetailHandler extends ContestDetailBaseHandler {
await contest.setStatus(domainId, tid, this.user._id, { subscribe: subscribe ? 1 : 0 });
this.back();
}
@param('tid', Types.ObjectId)
async postEarlyEnd(domainId: string, tid: ObjectId) {
if (this.tdoc.rule === 'homework') throw new ContestNotFoundError(domainId, tid);
if (!this.tsdoc?.attend) throw new ContestNotAttendedError(domainId, tid);
if (!contest.isOngoing(this.tdoc, this.tsdoc)) throw new ContestNotLiveError(domainId, tid);
await contest.setStatus(domainId, tid, this.user._id, { endAt: new Date() });
this.back();
}
}
export class ContestPrintHandler extends ContestDetailBaseHandler {
@@ -698,10 +710,16 @@ export class ContestUserHandler extends ContestManagementBaseHandler {
@param('tid', Types.ObjectId)
async get(domainId: string, tid: ObjectId) {
const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).project({
uid: 1, attend: 1, startAt: 1, unrank: 1,
uid: 1, attend: 1, startAt: 1, unrank: 1, endAt: 1,
}).toArray();
for (const tsdoc of tsdocs) {
tsdoc.endAt = (this.tdoc.duration && tsdoc.startAt) ? moment(tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate() : null;
if (this.tdoc.duration && tsdoc.startAt) {
tsdoc.endAt = moment.min([
moment(tsdoc.startAt).add(this.tdoc.duration, 'hours'),
moment(this.tdoc.endAt),
...(tsdoc.endAt ? [moment(tsdoc.endAt)] : []),
]).toDate();
}
}
const udict = await user.getListForRender(
domainId, [this.tdoc.owner, ...tsdocs.map((i) => i.uid)],
@@ -728,6 +746,20 @@ export class ContestUserHandler extends ContestManagementBaseHandler {
await contest.setStatus(domainId, tid, uid, { unrank: !tsdoc.unrank });
this.back();
}
@param('tid', Types.ObjectId)
@param('uid', Types.PositiveInt)
async postResume(domainId: string, tid: ObjectId, uid: number) {
const tsdoc = await contest.getStatus(domainId, tid, uid);
if (!tsdoc?.attend) throw new ContestNotAttendedError(uid);
if (this.tdoc.endAt <= new Date()) throw new ContestNotLiveError(domainId, tid);
if (this.tdoc.duration && tsdoc.startAt) {
const durationEnd = moment(tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate();
if (durationEnd <= new Date()) throw new ContestNotLiveError(domainId, tid);
}
await contest.setStatus(domainId, tid, uid, null, { endAt: '' });
this.back();
}
}
export class ContestBalloonHandler extends ContestManagementBaseHandler {