diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index aaff7eb74..7e7f1b613 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -92,10 +92,6 @@ export class ContestDetailBaseHandler extends Handler { @param('tid', Types.ObjectID, true) async __prepare(domainId: string, tid: ObjectID) { - if (this.session.tid && this.session.tid !== tid) { - // TODO: you are already in a contest - throw new ForbiddenError(); - } if (!tid) return; // ProblemDetailHandler also extends from ContestDetailBaseHandler [this.tdoc, this.tsdoc] = await Promise.all([ contest.get(domainId, tid), @@ -122,7 +118,7 @@ export class ContestDetailBaseHandler extends Handler { name: 'contest_main', args: {}, displayName: 'Back to contest list', - checker: () => !(this.tdoc.access & contest.AccessControl.LOCK_OTHER), + checker: () => true, }, { name: 'contest_detail', @@ -161,10 +157,6 @@ export class ContestDetailHandler extends Handler { contest.getStatus(domainId, tid, this.user._id), ]); if (contest.RULES[this.tdoc.rule].hidden) throw new ContestNotFoundError(domainId, tid); - if (this.session.tid && this.session.tid !== tid) { - // TODO: you are already in a contest - throw new ForbiddenError(); - } if (this.tdoc.assign?.length && !this.user.own(this.tdoc)) { const groups = await user.listGroup(domainId, this.user._id); if (!Set.intersection(this.tdoc.assign, groups.map((i) => i.name)).size) { @@ -193,7 +185,7 @@ export class ContestDetailHandler extends Handler { name: 'contest_main', args: {}, displayName: 'Back to contest list', - checker: () => !(this.tdoc.access & contest.AccessControl.LOCK_OTHER), + checker: () => true, }, { name: 'contest_detail', @@ -225,9 +217,11 @@ export class ContestDetailHandler extends Handler { async postAttend(domainId: string, tid: ObjectID, code = '') { if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(tid); if (this.tdoc._code && code !== this.tdoc._code && !this.tdoc.uninvite) throw new InvalidTokenError('Contest Invitation', code); - await contest.attend(domainId, tid, this.user._id); - if (this.tdoc.unrankUser.includes(this.user._id) - || (this.tdoc._code && code !== this.tdoc._code && this.tdoc.uninvite)) contest.setStatus(domainId, tid, this.user._id, { unrank: true }); + await contest.attend( + domainId, tid, this.user._id, + (this.tdoc._code && code !== this.tdoc._code && this.tdoc.uninvite) + ? { unrank: true } : {}, + ); this.back(); } } diff --git a/packages/hydrooj/src/handler/training.ts b/packages/hydrooj/src/handler/training.ts index 7339348b7..1a1a2f61e 100644 --- a/packages/hydrooj/src/handler/training.ts +++ b/packages/hydrooj/src/handler/training.ts @@ -258,14 +258,14 @@ export class TrainingFilesHandler extends Handler { @param('tid', Types.ObjectID) @post('filename', Types.Name, true) async postUploadFile(domainId: string, tid: ObjectID, filename: string) { - if ((this.tdoc.files?.length || 0) >= system.get('limit.training_files')) { + if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) { throw new FileLimitExceededError('count'); } const file = this.request.files?.file; if (!file) throw new ValidationError('file'); const f = statSync(file.filepath); const size = Math.sum((this.tdoc.files || []).map((i) => i.size)) + f.size; - if (size >= system.get('limit.training_files_size')) { + if (size >= system.get('limit.contest_files_size')) { throw new FileLimitExceededError('size'); } if (!filename) filename = file.originalFilename || String.random(16); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index d6e12bdad..e9d9c6ca5 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -366,9 +366,7 @@ export interface Tdoc(cursor: Cursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; +type ranked = >(cursor: Cursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; const ranked: ranked = async (cursor, equ) => { let last = null; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index e2a347d1d..b4f5b75c7 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -127,7 +127,7 @@ const acm = buildContestRule({ async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) { const tsddict = tsdoc.detail || {}; const row: ScoreboardRow = [ - { type: 'rank', value: rank ? rank.toString() : '*', raw: rank }, + { type: 'rank', value: rank.toString() }, { type: 'user', value: udoc.uname, raw: tsdoc.uid }, ]; if (isExport) { @@ -263,7 +263,7 @@ const oi = buildContestRule({ async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) { const tsddict = tsdoc.detail || {}; const row: ScoreboardNode[] = [ - { type: 'rank', value: rank ? rank.toString() : '*', raw: rank }, + { type: 'rank', value: rank.toString() }, { type: 'user', value: udoc.uname, raw: tsdoc.uid }, ]; if (isExport) { @@ -510,13 +510,6 @@ export const RULES: ContestRules = { acm, oi, homework, ioi, }; -export enum AccessControl { - FREE, - LOCK_OTHER = 2, - UNIQUE_LOGIN = 4, - CLIENT_REQUIRED = 8, -} - function _getStatusJournal(tsdoc) { return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime)); } @@ -600,9 +593,9 @@ export async function getListStatus(domainId: string, uid: number, tids: ObjectI return r; } -export async function attend(domainId: string, tid: ObjectID, uid: number) { +export async function attend(domainId: string, tid: ObjectID, uid: number, payload: any = {}) { try { - await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1); + await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1, payload); } catch (e) { throw new ContestAlreadyAttendedError(tid, uid); } @@ -753,7 +746,6 @@ export const statusText = (tdoc: Tdoc, tsdoc?: any) => ( global.Hydro.model.contest = { RULES, - AccessControl, add, getListStatus, getMultiStatus, diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 2a764a033..3388e2c8a 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -350,12 +350,15 @@ export async function setStatusIfNotCondition( export async function cappedIncStatus( domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number, key: NumberKeys, value: number, minValue = -1, maxValue = 1, + setPayload: Partial = {}, ): Promise { assert(value !== 0); const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue }; + const operation = { $inc: { [key]: value } } as UpdateQuery; + if (Object.keys(setPayload).length) operation.$set = setPayload; const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, [key]: { $not } }, - { $inc: { [key]: value } }, + operation, { upsert: true, returnDocument: 'after' }, ); return res.value; diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 054f47ecd..ea60bcef9 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -213,10 +213,8 @@ SystemSetting( Setting('setting_limits', 'limit.problem_files_max_size', 256 * 1024 * 1024, 'number', 'limit.problem_files_max_size', 'Max files size per problem'), Setting('setting_limits', 'limit.user_files', 100, 'number', 'limit.user_files', 'Max files for user'), Setting('setting_limits', 'limit.user_files_size', 128 * 1024 * 1024, 'number', 'limit.user_files_size', 'Max total file size for user'), - Setting('setting_limits', 'limit.contest_files', 100, 'number', 'limit.contest_files', 'Max files for contest'), - Setting('setting_limits', 'limit.contest_files_size', 128 * 1024 * 1024, 'number', 'limit.contest_files_size', 'Max total file size for contest'), - Setting('setting_limits', 'limit.training_files', 100, 'number', 'limit.training_files', 'Max files for training'), - Setting('setting_limits', 'limit.training_files_size', 128 * 1024 * 1024, 'number', 'limit.training_files_size', 'Max total file size for training'), + Setting('setting_limits', 'limit.contest_files', 100, 'number', 'limit.contest_files', 'Max files for contest or training'), + Setting('setting_limits', 'limit.contest_files_size', 128 * 1024 * 1024, 'number', 'limit.contest_files_size', 'Max total file size for contest or training'), Setting('setting_limits', 'limit.submission', 60, 'number', 'limit.submission', 'Max submission count per minute'), Setting('setting_limits', 'limit.submission_user', 15, 'number', 'limit.submission_user', 'Max submission count per user per minute'), Setting('setting_limits', 'limit.pretest', 60, 'number', 'limit.pretest', 'Max pretest count per minute'), diff --git a/packages/ui-default/pages/contest_scoreboard.page.ts b/packages/ui-default/pages/contest_scoreboard.page.ts index d88193a39..5c84228c3 100644 --- a/packages/ui-default/pages/contest_scoreboard.page.ts +++ b/packages/ui-default/pages/contest_scoreboard.page.ts @@ -3,35 +3,23 @@ import { NamedPage } from 'vj/misc/Page'; const page = new NamedPage('contest_scoreboard', () => { const { tdoc } = UiContext; - let starData = JSON.parse(localStorage.getItem('Hydro.starData')) || {}; - Object.keys(starData).forEach((key) => { - if (starData[key].expire < Date.now()) delete starData[key]; - }); - if (!starData[tdoc.docId]) { - starData[tdoc.docId] = { - expire: tdoc.endAt + 1000 * 60 * 60 * 24 * 7, - starUids: [], - }; - } else { - starData[tdoc.docId].starUids.forEach((uid) => { - console.log(uid); - $(`.star.user--${uid}`).addClass('activated'); - }); - } - console.log(starData[tdoc.docId].starUids); - localStorage.setItem('Hydro.starData', JSON.stringify(starData)); + const key = `scoreboard-star/${tdoc.domainId}/${tdoc.docId}`; + const read = () => JSON.parse(localStorage.getItem(key) || '[]'); + const write = (data) => localStorage.setItem(key, JSON.stringify(data)); + + read().forEach((uid) => $(`.star.user--${uid}`).addClass('activated')); $('.star').on('click', (e) => { - starData = JSON.parse(localStorage.getItem('Hydro.starData')); + const star = read(); const $target = $(e.currentTarget); const uid = $target.data('uid'); - if (starData[tdoc.docId].starUids.includes(uid)) { + if (star.includes(uid)) { $target.removeClass('activated'); - starData[tdoc.docId].starUids.splice(starData[tdoc.docId].starUids.indexOf(uid), 1); + star.splice(star.indexOf(uid), 1); } else { $target.addClass('activated'); - starData[tdoc.docId].starUids.push(uid); + star.push(uid); } - localStorage.setItem('Hydro.starData', JSON.stringify(starData)); + write(star); }); $('.select.filter').on('change', (e) => { @@ -40,9 +28,7 @@ const page = new NamedPage('contest_scoreboard', () => { $('.data-table tbody tr').show(); } else if (val === 'star') { $('.data-table tbody tr').hide(); - starData[tdoc.docId].starUids.forEach((uid) => { - $(`.star.user--${uid}`).closest('tr').show(); - }); + read().forEach((uid) => $(`.star.user--${uid}`).closest('tr').show()); } else { $('.data-table tbody tr').hide(); $('.rank--unrank').closest('tr').show(); diff --git a/packages/ui-default/templates/contest_scoreboard.html b/packages/ui-default/templates/contest_scoreboard.html index 6d2a03ee3..214767403 100644 --- a/packages/ui-default/templates/contest_scoreboard.html +++ b/packages/ui-default/templates/contest_scoreboard.html @@ -35,7 +35,6 @@ -

{{ tdoc.title }}

{% if model.contest.isLocked(tdoc) %}
@@ -78,7 +77,7 @@ {%- for column in row -%} {%- if column.type == 'rank' -%} - {{ column.value|string|nl2br|safe }} + {{ '*' if column.value == '0' else column.value|string|nl2br|safe }} {%- elif column.type == 'user' -%} {%- set canView = canViewAll or handler.user._id == column.raw -%}