diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index ea8eb6780..7515c8477 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -193,10 +193,9 @@ export class ContestBroadcastHandler extends ContestDetailBaseHandler { export class ContestScoreboardHandler extends ContestDetailBaseHandler { @param('tid', Types.ObjectID) - @param('page', Types.PositiveInt, true) @param('ext', Types.Range(['csv', 'html']), true) @param('ignoreLock', Types.Boolean, true) - async get(domainId: string, tid: ObjectID, page = 1, ext = '', ignoreLock = false) { + async get(domainId: string, tid: ObjectID, ext = '', ignoreLock = false) { if (ignoreLock && !this.user.own(this.tdoc)) { this.checkPerm(this.tdoc.rule === 'homework' ? PERM.PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD @@ -210,14 +209,14 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { // Problem statistics display is allowed as we can view submission info in scoreboard. ...problem.PROJECTION_CONTEST_LIST, 'nSubmit', 'nAccept', ]); - const [, rows, udict, , nPages] = await contest.getScoreboard.call(this, domainId, tid, false, page, ignoreLock); + const [, rows, udict] = await contest.getScoreboard.call(this, domainId, tid, false, ignoreLock); this.response.template = 'contest_scoreboard.html'; // eslint-disable-next-line @typescript-eslint/naming-convention const page_name = this.tdoc.rule === 'homework' ? 'homework_scoreboard' : 'contest_scoreboard'; this.response.body = { - tdoc: this.tdoc, rows, udict, nPages, page, pdict, page_name, + tdoc: this.tdoc, rows, udict, pdict, page_name, }; } @@ -227,7 +226,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value?.toString().replace(/\n/g, ' ')).join(','))).join('\n')}`, html: (rows, tdoc) => this.renderHTML('contest_scoreboard_download_html.html', { rows, tdoc }), }; - const [, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0, ignoreLock); + const [, rows] = await contest.getScoreboard.call(this, domainId, tid, true, ignoreLock); this.binary(await getContent[ext](rows, this.tdoc), `${this.tdoc.title}.${ext}`); } } diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 7ed11efd1..0c40dc2e0 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -514,9 +514,9 @@ export interface ContestRule { ) => Promise; scoreboard: ( this: ContestRule, isExport: boolean, _: (s: string) => string, - tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor, page: number, - ) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>; - ranked: (tdoc: Tdoc<30>, cursor: Cursor) => Promise<[Array<[number, ContestStat & T]>, number]>; + tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor, + ) => Promise<[board: ScoreboardRow[], udict: Udict]>; + ranked: (tdoc: Tdoc<30>, cursor: Cursor) => Promise<[number, ContestStat & T][]>; } export type ContestRules = Dictionary; diff --git a/packages/hydrooj/src/lib/rank.ts b/packages/hydrooj/src/lib/rank.ts index c0ebbb41f..6e42f11a7 100644 --- a/packages/hydrooj/src/lib/rank.ts +++ b/packages/hydrooj/src/lib/rank.ts @@ -1,21 +1,20 @@ import { Cursor } from 'mongodb'; -import paginate from './paginate'; -type ranked = (cursor: Cursor, equ: ((a: T, b: T) => boolean), page?: number) => Promise<[[number, T][], number]>; +type ranked = (cursor: Cursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; -const ranked: ranked = async (cursor, equ, page) => { +const ranked: ranked = async (cursor, equ) => { let last = null; - let r = page ? (page - 1) * 100 : 0; - let count = page ? r : 0; + let r = 0; + let count = 0; const results = []; - const [docs, nPages] = page ? await paginate(cursor, page, 100) : [await cursor.toArray(), 1]; + const docs = await cursor.toArray(); for (const doc of docs) { count++; if (!last || !equ(last, doc)) r = count; last = doc; results.push([r, doc]); } - return [results, nPages]; + return results; }; global.Hydro.lib.rank = ranked; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index d50b2267a..f23116410 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -154,8 +154,8 @@ const acm = buildContestRule({ } return row; }, - async scoreboard(isExport, _, tdoc, pdict, cursor, page) { - const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time, page); + async scoreboard(isExport, _, tdoc, pdict, cursor) { + const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getList(tdoc.domainId, uids); // Find first accept @@ -186,7 +186,7 @@ const acm = buildContestRule({ ), )), ]; - return [rows, udict, nPages]; + return [rows, udict]; }, async ranked(tdoc, cursor) { return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); @@ -272,8 +272,8 @@ const oi = buildContestRule({ } return row; }, - async scoreboard(isExport, _, tdoc, pdict, cursor, page) { - const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page); + async scoreboard(isExport, _, tdoc, pdict, cursor) { + const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getList(tdoc.domainId, uids); const psdict = {}; @@ -307,7 +307,7 @@ const oi = buildContestRule({ ), )), ]; - return [rows, udict, nPages]; + return [rows, udict]; }, async ranked(tdoc, cursor) { return await ranked(cursor, (a, b) => a.score === b.score); @@ -453,8 +453,8 @@ const homework = buildContestRule({ } return row; }, - async scoreboard(isExport, _, tdoc, pdict, cursor, page) { - const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page); + async scoreboard(isExport, _, tdoc, pdict, cursor) { + const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getList(tdoc.domainId, uids); const columns = await this.scoreboardHeader(isExport, _, tdoc, pdict); @@ -464,7 +464,7 @@ const homework = buildContestRule({ ([rank, tsdoc]) => this.scoreboardRow(isExport, _, tdoc, pdict, udict[tsdoc.uid], rank, tsdoc), )), ]; - return [rows, udict, nPages]; + return [rows, udict]; }, async ranked(tdoc, cursor) { return await ranked(cursor, (a, b) => a.score === b.score); @@ -679,18 +679,18 @@ export function canShowScoreboard(tdoc: Tdoc<30>, allowPermOverride = true) { export async function getScoreboard( this: Handler, domainId: string, tid: ObjectID, - isExport = false, page: number, ignoreLock = false, -): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict, number]> { + isExport = false, ignoreLock = false, +): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict]> { const tdoc = await get(domainId, tid); if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid); if (ignoreLock) delete tdoc.lockAt; const tsdocsCursor = getMultiStatus(domainId, { docId: tid }).sort(RULES[tdoc.rule].statusSort); const pdict = await problem.getList(domainId, tdoc.pids, true); - const [rows, udict, nPages] = await RULES[tdoc.rule].scoreboard( + const [rows, udict] = await RULES[tdoc.rule].scoreboard( isExport, this.translate.bind(this), - tdoc, pdict, tsdocsCursor, page, + tdoc, pdict, tsdocsCursor, ); - return [tdoc, rows, udict, pdict, nPages]; + return [tdoc, rows, udict, pdict]; } export const statusText = (tdoc: Tdoc, tsdoc?: any) => ( diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 2a76b728d..b650c5710 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -1,4 +1,4 @@ -import { escapeRegExp, pick } from 'lodash'; +import { escapeRegExp, pick, uniq } from 'lodash'; import LRU from 'lru-cache'; import { Collection, ObjectID } from 'mongodb'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; @@ -21,7 +21,7 @@ export const coll: Collection = db.collection('user'); // Virtual user, only for display in contest. export const collV: Collection = db.collection('vuser'); export const collGroup: Collection = db.collection('user.group'); -const cache = new LRU({ max: 500, ttl: 300 * 1000 }); +const cache = new LRU({ max: 10000, ttl: 300 * 1000 }); export function deleteUserCache(udoc: User | Udoc | string | undefined | null, receiver = false) { if (!udoc) return; @@ -192,10 +192,10 @@ class UserModel { } static async getList(domainId: string, uids: number[]): Promise { - const _uids = new Set(uids); const r: Udict = {}; - // eslint-disable-next-line no-await-in-loop - for (const uid of _uids) r[uid] = (await UserModel.getById(domainId, uid)) || new User(UserModel.defaultUser, {}); + await Promise.all(uniq(uids).map(async (uid) => { + r[uid] = (await UserModel.getById(domainId, uid)) || new User(UserModel.defaultUser, {}); + })); return r; } diff --git a/packages/ui-default/templates/contest_scoreboard.html b/packages/ui-default/templates/contest_scoreboard.html index 253b29b0a..c24ba3416 100644 --- a/packages/ui-default/templates/contest_scoreboard.html +++ b/packages/ui-default/templates/contest_scoreboard.html @@ -74,7 +74,6 @@ {% endif %}{%- endfor -%} - {{ paginator.render(page, nPages) }}