core: fix contest paginator

This commit is contained in:
undefined committed 2021-05-03 02:00:20 +08:00
1 parent 0d7c53b927
commit 2a3933de1a
14 files changed
+46 -40

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.23.31",
"version": "2.24.0",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+5 -4
View File
@@ -136,9 +136,9 @@ class ContestBoardcastHandler extends Handler {
class ContestScoreboardHandler extends Handler {
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt)
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
const [tdoc, rows, udict] = await contest.getScoreboard(domainId, tid, false, page);
const [tdoc, rows, udict, , nPages] = await contest.getScoreboard.call(this, domainId, tid, false, page);
const path = [
['Hydro', 'homepage'],
['contest_main', 'contest_main'],
@@ -147,7 +147,7 @@ class ContestScoreboardHandler extends Handler {
];
this.response.template = 'contest_scoreboard.html';
this.response.body = {
tdoc, rows, path, udict,
tdoc, rows, path, udict, nPages, page,
};
}
}
@@ -156,11 +156,12 @@ class ContestScoreboardDownloadHandler extends Handler {
@param('tid', Types.ObjectID)
@param('ext', Types.Range(['csv', 'html']))
async get(domainId: string, tid: ObjectID, ext: string) {
await this.limitRate('scoreboard_download', 120, 3);
const getContent = {
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
};
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0);
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0);
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
}
}
+6 -4
View File
@@ -356,8 +356,9 @@ class HomeworkScoreboardHandler extends Handler {
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
const [tdoc, rows, udict] = await contest.getScoreboard(
domainId, tid, false, page, document.TYPE_HOMEWORK,
const [tdoc, rows, udict, , nPages] = await contest.getScoreboard.call(
this, domainId, tid, false,
page, document.TYPE_HOMEWORK,
);
const path = [
['Hydro', 'homepage'],
@@ -367,7 +368,7 @@ class HomeworkScoreboardHandler extends Handler {
];
this.response.template = 'contest_scoreboard.html';
this.response.body = {
tdoc, rows, path, udict, page_name: 'homework_scoreboard',
tdoc, rows, path, udict, page, nPages, page_name: 'homework_scoreboard',
};
}
}
@@ -376,12 +377,13 @@ class HomeworkScoreboardDownloadHandler extends Handler {
@param('tid', Types.ObjectID)
@param('ext', Types.Name)
async get(domainId: string, tid: ObjectID, ext: string) {
await this.limitRate('scoreboard_download', 120, 3);
const getContent = {
csv: (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
};
if (!getContent[ext]) throw new ValidationError('ext');
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0, document.TYPE_HOMEWORK);
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0, document.TYPE_HOMEWORK);
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
}
}
+1 -1
View File
@@ -409,7 +409,7 @@ export interface ContestRule {
scoreboard: (
isExport: boolean, _: (s: string) => string,
tdoc: Tdoc<30 | 60>, pdict: Pdict, cursor: Cursor<any>, page: number,
) => Promise<[board: ScoreboardRow[], udict: Udict]>;
) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>;
ranked: (tdoc: Tdoc<30 | 60>, cursor: Cursor<any>) => Promise<any[]>;
}
+9 -8
View File
@@ -3,24 +3,25 @@ import paginate from './paginate';
type G<T> = Promise<[[number, T], () => G<T>] | []>;
interface ranked {
<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean, page: number): Promise<[number, T][]>;
<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean, page: number): Promise<[[number, T][], number]>;
iterate<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): G<T>;
all<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): Promise<[number, T][]>;
all<T>(cursor: Cursor<T>, equ: (a: T, b: T) => boolean): Promise<[[number, T][], 1]>;
}
const ranked: ranked = async function rankedPage(cursor, equ = (a, b) => a === b, page) {
if (!page) return await ranked.all(cursor, equ);
let last = null;
let r = (page - 1) * 100;
let count = r;
const results = [];
const [docs] = await paginate(cursor, page, 100);
const [docs, nPages] = await paginate(cursor, page, 100);
for (const doc of docs) {
count++;
if (count === 1 || !equ(last, doc)) r = count;
if (!last || !equ(last, doc)) r = count;
last = doc;
results.push([r, doc]);
}
return results;
return [results, nPages];
};
ranked.iterate = async function rankedIterate<T>(cursor: Cursor<T>, equ = (a, b) => a === b) {
let last = null;
@@ -30,7 +31,7 @@ ranked.iterate = async function rankedIterate<T>(cursor: Cursor<T>, equ = (a, b)
if (!await cursor.hasNext()) return [];
const doc = await cursor.next();
count++;
if (count === 1 || !equ(last, doc)) r = count;
if (!last || !equ(last, doc)) r = count;
last = doc;
return [[r, doc], getNext];
}
@@ -44,11 +45,11 @@ ranked.all = async function rankedAll(cursor, equ = (a, b) => a === b) {
const docs = await cursor.toArray();
for (const doc of docs) {
count++;
if (count === 1 || !equ(last, doc)) r = count;
if (!last || !equ(last, doc)) r = count;
last = doc;
results.push([r, doc]);
}
return results;
return [results, 1];
};
global.Hydro.lib.rank = ranked;
+11 -11
View File
@@ -53,7 +53,7 @@ const acm: ContestRule = {
return { accept, time, detail };
},
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList(tdoc.domainId, uids);
const columns: ScoreboardRow = [
@@ -139,7 +139,7 @@ const acm: ContestRule = {
row.raw = tsdoc;
rows.push(row);
}
return [rows, udict];
return [rows, udict, nPages];
},
async ranked(tdoc, cursor) {
return await ranked.all(cursor, (a, b) => a.score === b.score);
@@ -162,7 +162,7 @@ const oi: ContestRule = {
showScoreboard: (tdoc, now) => now > tdoc.endAt,
showRecord: (tdoc, now) => now > tdoc.endAt,
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList(tdoc.domainId, uids);
const columns: ScoreboardNode[] = [
@@ -205,7 +205,7 @@ const oi: ContestRule = {
}
rows.push(row);
}
return [rows, udict];
return [rows, udict, nPages];
},
async ranked(tdoc, cursor) {
return await ranked.all(cursor, (a, b) => a.score === b.score);
@@ -268,7 +268,7 @@ const homework: ContestRule = {
showScoreboard: () => true,
showRecord: () => true,
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score, page);
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList.call(this, tdoc.domainId, uids);
const columns: ScoreboardNode[] = [
@@ -358,7 +358,7 @@ const homework: ContestRule = {
}
rows.push(row);
}
return [rows, udict];
return [rows, udict, nPages];
},
async ranked(tdoc, cursor) {
return await ranked.all(cursor, (a, b) => a.score === b.score);
@@ -564,17 +564,17 @@ export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true)
export async function getScoreboard(
domainId: string, tid: ObjectID,
isExport = false, page: number, docType: 30 | 60 = document.TYPE_CONTEST,
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, Pdict]> {
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, Pdict, number]> {
const tdoc = await get(domainId, tid, docType);
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
if (!this.canShowRecord(tdoc)) throw new ContestScoreboardHiddenError(tid);
if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
if (!canShowRecord.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort);
const pdict = await problem.getList(domainId, tdoc.pids, true);
const [rows, udict] = await RULES[tdoc.rule].scoreboard(
const [rows, udict, nPages] = await RULES[tdoc.rule].scoreboard(
isExport, this.translate.bind(this),
tdoc, pdict, tsdocs, page,
);
return [tdoc, rows, udict, pdict];
return [tdoc, rows, udict, pdict, nPages];
}
export async function verifyProblems(domainId: string, pids: Array<number | string>) {
+1 -1
View File
@@ -17,7 +17,7 @@ import * as bus from '../service/bus';
const coll: Collection<Udoc> = db.collection('user');
const logger = new Logger('model/user');
const cache = new LRU<string, User>(500);
const cache = new LRU<string, User>({ max: 500, maxAge: 300 * 1000 });
function deleteUserCache(udoc: User | Udoc) {
if (udoc) {
+2 -2
View File
@@ -468,7 +468,7 @@ export class Handler extends HandlerCommon {
this.response.body = data;
this.response.template = null;
this.response.type = 'application/octet-stream';
if (name) this.response.disposition = `attachment; filename="${name}"`;
if (name) this.response.disposition = `attachment; filename="${encodeURIComponent(name)}"`;
}
async getSession() {
@@ -567,7 +567,7 @@ export class Handler extends HandlerCommon {
}
async putResponse() {
if (this.response.disposition) this.ctx.set('Content-Disposition', encodeURIComponent(this.response.disposition));
if (this.response.disposition) this.ctx.set('Content-Disposition', this.response.disposition);
if (!this.response.body) return;
if (this.response.redirect && !this.request.json) {
this.ctx.response.type = 'application/octet-stream';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.6.22",
"version": "4.6.23",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",
@@ -45,7 +45,7 @@
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
{% if psdict[pid] and psdict[pid].rid %}
{% set rdoc = rdict[psdict[pid].rid] %}
{% if handler.canShowRecord(tdoc) %}
{% if model.contest.canShowRecord.call(handler, tdoc) %}
{{ record.render_status_td(rdoc) }}
{% else %}
<td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td>
@@ -1,3 +1,4 @@
{% import "components/paginator.html" as paginator with context %}
{% extends "layout/basic.html" %}
{% set type = 'homework' if tdoc.docType == 60 else 'contest' %}
{% block content %}
@@ -32,7 +33,7 @@
</tr>
</thead>
<tbody>
{%- for row in rows -%}{% if loop.index0>0 %}
{%- for row in rows -%}{% if loop.index0 > 0 %}
<tr>
{%- for column in row -%}
<td class="col--{{ rows[0][loop.index0]['type'] }}">
@@ -49,6 +50,7 @@
{% endif %}{%- endfor -%}
</tbody>
</table>
{{ paginator.render(page, nPages) }}
</div>
</div>
</div></div>
@@ -44,7 +44,7 @@
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
{% if psdict[pid] and psdict[pid]['rid'] %}
{% set rdoc = rdict[psdict[pid]['rid']] %}
{% if handler.canShowRecord(tdoc) %}
{% if model.contest.canShowRecord.call(handler, tdoc) %}
{{ record.render_status_td(rdoc) }}
{% else %}
<td class="col--status record-status--border"><a href="{{ url('record_detail', rid=rdoc._id) }}">{{ _('Submitted') }}</a></td>
@@ -43,11 +43,11 @@
{% endif %}
</li>
{% endif %}
{% if handler.canShowScoreboard(tdoc, False) %}
{% if model.contest.canShowScoreboard.call(handler, tdoc, False) %}
<li class="menu__item"><a class="menu__link" href="{{ url('contest_scoreboard', tid=tdoc.docId) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
</a></li>
{% elif handler.canViewHiddenScoreboard(tdoc) %}
{% elif model.contest.canViewHiddenScoreboard.call(handler, tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ url('contest_scoreboard', tid=tdoc.docId) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
</a></li>
@@ -43,11 +43,11 @@
{% endif %}
</li>
{% endif %}
{% if handler.canShowScoreboard(tdoc, false) %}
{% if model.contest.canShowScoreboard.call(handler, tdoc, false) %}
<li class="menu__item"><a class="menu__link" href="{{ url('homework_scoreboard', tid=tdoc.docId) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
</a></li>
{% elif handler.canViewHiddenScoreboard(tdoc) %}
{% elif model.contest.canViewHiddenScoreboard(handler, tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ url('homework_scoreboard', tid=tdoc.docId) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
</a></li>