mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-18 01:36:08 +08:00
core: performance: userCache&scoreboard.pagination
This commit is contained in:
1 parent
0454acdbf9
commit
0d7c53b927
12 files changed
+244
-166
No files matched your search
@@ -26,6 +26,7 @@
|
||||
"koa-router": "^10.0.0",
|
||||
"koa-static-cache": "^5.1.3",
|
||||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^6.0.0",
|
||||
"minio": "7.0.17",
|
||||
"moment-timezone": "^0.5.32",
|
||||
"mongodb": "^3.6.6",
|
||||
@@ -47,6 +48,7 @@
|
||||
"@types/koa-router": "^7.4.1",
|
||||
"@types/koa-static-cache": "^5.1.0",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"@types/minio": "^7.0.7",
|
||||
"@types/mongodb": "^3.6.12",
|
||||
"@types/nodemailer": "^6.4.1",
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||
ContestNotAttendedError, PermissionError,
|
||||
} from '../error';
|
||||
import { Pdoc, Tdoc, User } from '../interface';
|
||||
import paginate from '../lib/paginate';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import * as contest from '../model/contest';
|
||||
@@ -22,9 +23,7 @@ import {
|
||||
import * as bus from '../service/bus';
|
||||
import storage from '../service/storage';
|
||||
|
||||
const ContestHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
class ContestListHandler extends ContestHandler {
|
||||
class ContestListHandler extends Handler {
|
||||
@param('rule', Types.Range(contest.RULES), true)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, rule = '', page = 1) {
|
||||
@@ -54,7 +53,7 @@ class ContestListHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestDetailHandler extends ContestHandler {
|
||||
class ContestDetailHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
@@ -70,7 +69,7 @@ class ContestDetailHandler extends ContestHandler {
|
||||
if (tsdoc) {
|
||||
attended = tsdoc.attend === 1;
|
||||
for (const pdetail of tsdoc.journal || []) psdict[pdetail.pid] = pdetail;
|
||||
if (this.canShowRecord(tdoc)) {
|
||||
if (contest.canShowRecord.call(this, tdoc)) {
|
||||
const q = [];
|
||||
for (const i in psdict) q.push(psdict[i].rid);
|
||||
rdict = await record.getList(domainId, q, true);
|
||||
@@ -106,7 +105,7 @@ class ContestDetailHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestBoardcastHandler extends ContestHandler {
|
||||
class ContestBoardcastHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const tdoc = await contest.get(domainId, tid);
|
||||
@@ -135,10 +134,11 @@ class ContestBoardcastHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestScoreboardHandler extends ContestHandler {
|
||||
class ContestScoreboardHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const [tdoc, rows, udict] = await this.getScoreboard(domainId, tid);
|
||||
@param('page', Types.PositiveInt)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
const [tdoc, rows, udict] = await contest.getScoreboard(domainId, tid, false, page);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['contest_main', 'contest_main'],
|
||||
@@ -152,14 +152,7 @@ class ContestScoreboardHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestScoreboardRawHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
this.response.body = await this.getRawStatus(domainId, tid);
|
||||
}
|
||||
}
|
||||
|
||||
class ContestScoreboardDownloadHandler extends ContestHandler {
|
||||
class ContestScoreboardDownloadHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.Range(['csv', 'html']))
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
@@ -167,12 +160,14 @@ class ContestScoreboardDownloadHandler extends ContestHandler {
|
||||
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 this.getScoreboard(domainId, tid, true);
|
||||
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0);
|
||||
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
class ContestEditHandler extends ContestHandler {
|
||||
class ContestEditHandler extends Handler {
|
||||
tdoc: Tdoc;
|
||||
|
||||
@param('tid', Types.ObjectID)
|
||||
async prepare(domainId: string, tid: ObjectID) {
|
||||
this.tdoc = await contest.get(domainId, tid);
|
||||
@@ -229,7 +224,7 @@ class ContestEditHandler extends ContestHandler {
|
||||
const endAt = beginAtMoment.clone().add(duration, 'hours').toDate();
|
||||
if (beginAtMoment.isSameOrAfter(endAt)) throw new ValidationError('duration');
|
||||
const beginAt = beginAtMoment.toDate();
|
||||
pids = await this.verifyProblems(domainId, pids);
|
||||
pids = await contest.verifyProblems(domainId, pids);
|
||||
await contest.edit(domainId, this.tdoc.docId, {
|
||||
title, content, rule, beginAt, endAt, pids, rated,
|
||||
});
|
||||
@@ -242,7 +237,13 @@ class ContestEditHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestProblemHandler extends ContestHandler {
|
||||
class ContestProblemHandler extends Handler {
|
||||
tdoc: Tdoc<30 | 60>;
|
||||
pdoc: Pdoc;
|
||||
tsdoc: any;
|
||||
udoc: User;
|
||||
attended: boolean;
|
||||
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async _prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
@@ -308,7 +309,7 @@ class ContestDetailProblemSubmitHandler extends ContestProblemHandler {
|
||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
if (!contest.isOngoing(this.tdoc)) throw new ContestNotLiveError(this.tdoc.docId);
|
||||
let rdocs = [];
|
||||
if (this.canShowRecord(this.tdoc)) {
|
||||
if (contest.canShowRecord.call(this, this.tdoc)) {
|
||||
rdocs = await record.getUserInProblemMulti(
|
||||
domainId, this.user._id,
|
||||
this.pdoc.docId, true,
|
||||
@@ -326,7 +327,7 @@ class ContestDetailProblemSubmitHandler extends ContestProblemHandler {
|
||||
tdoc: this.tdoc,
|
||||
pdoc: this.pdoc,
|
||||
udoc: this.udoc,
|
||||
attended: this.attend,
|
||||
attended: this.attended,
|
||||
path,
|
||||
rdocs,
|
||||
page_name: 'contest_detail_problem_submit',
|
||||
@@ -349,7 +350,7 @@ class ContestDetailProblemSubmitHandler extends ContestProblemHandler {
|
||||
contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId),
|
||||
]);
|
||||
bus.boardcast('record/change', rdoc);
|
||||
if (!this.canShowRecord(this.tdoc)) {
|
||||
if (!contest.canShowRecord.call(this, this.tdoc)) {
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = this.url('contest_detail', { tid });
|
||||
} else {
|
||||
@@ -359,7 +360,7 @@ class ContestDetailProblemSubmitHandler extends ContestProblemHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestCodeHandler extends ContestHandler {
|
||||
class ContestCodeHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
if (!this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) {
|
||||
@@ -386,7 +387,7 @@ class ContestCodeHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestCreateHandler extends ContestHandler {
|
||||
class ContestCreateHandler extends Handler {
|
||||
async get() {
|
||||
this.response.template = 'contest_edit.html';
|
||||
const rules = {};
|
||||
@@ -431,7 +432,7 @@ class ContestCreateHandler extends ContestHandler {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
}
|
||||
const endAt = beginAt.clone().add(duration, 'hours');
|
||||
pids = await this.verifyProblems(domainId, pids);
|
||||
pids = await contest.verifyProblems(domainId, pids);
|
||||
const tid = await contest.add(
|
||||
domainId, title, content,
|
||||
this.user._id, rule, beginAt.toDate(), endAt.toDate(), pids, rated,
|
||||
@@ -448,7 +449,6 @@ export async function apply() {
|
||||
Route('contest_boardcast', '/contest/:tid/boardcast', ContestBoardcastHandler);
|
||||
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST);
|
||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST);
|
||||
Route('contest_scoreboard_raw', '/contest/:tid/scoreboard/raw', ContestScoreboardRawHandler, PERM.PERM_VIEW_CONTEST);
|
||||
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST);
|
||||
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST);
|
||||
Route('contest_detail_problem_file_download', '/contest/:tid/p/:pid/file/:filename', ContestProblemFileDownloadHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ValidationError, HomeworkNotLiveError, ProblemNotFoundError,
|
||||
HomeworkNotAttendedError,
|
||||
} from '../error';
|
||||
import { PenaltyRules } from '../interface';
|
||||
import { PenaltyRules, Tdoc, Pdoc } from '../interface';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
@@ -29,9 +29,7 @@ const validatePenaltyRules = (input: string) => {
|
||||
};
|
||||
const convertPenaltyRules = validatePenaltyRules;
|
||||
|
||||
const HomeworkHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
class HomeworkMainHandler extends HomeworkHandler {
|
||||
class HomeworkMainHandler extends Handler {
|
||||
async get({ domainId }) {
|
||||
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK).toArray();
|
||||
const calendar = [];
|
||||
@@ -54,7 +52,7 @@ class HomeworkMainHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkDetailHandler extends HomeworkHandler {
|
||||
class HomeworkDetailHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
@@ -72,7 +70,7 @@ class HomeworkDetailHandler extends HomeworkHandler {
|
||||
psdict[pdetail.pid] = pdetail;
|
||||
rdict[pdetail.rid] = { _id: pdetail.rid };
|
||||
}
|
||||
if (this.canShowRecord(tdoc) && tsdoc.journal) {
|
||||
if (contest.canShowRecord.call(this, tdoc) && tsdoc.journal) {
|
||||
rdict = await record.getList(
|
||||
domainId,
|
||||
tsdoc.journal.map((pdetail) => pdetail.rid),
|
||||
@@ -118,7 +116,11 @@ class HomeworkDetailHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkDetailProblemHandler extends HomeworkHandler {
|
||||
class HomeworkDetailProblemHandler extends Handler {
|
||||
tdoc: Tdoc<60>;
|
||||
pdoc: Pdoc;
|
||||
tsdoc: any;
|
||||
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
@@ -162,7 +164,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
|
||||
if (!attended) throw new HomeworkNotAttendedError(tid);
|
||||
if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid);
|
||||
if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid, tid);
|
||||
const rdocs = this.canShowRecord(this.tdoc)
|
||||
const rdocs = contest.canShowRecord.call(this, this.tdoc)
|
||||
? await record.getUserInProblemMulti(domainId, this.user._id, pid, true)
|
||||
.sort('_id', -1).limit(10).toArray()
|
||||
: [];
|
||||
@@ -202,12 +204,12 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
|
||||
]);
|
||||
bus.boardcast('record/change', rdoc);
|
||||
this.response.body = { tid, rid };
|
||||
if (this.canShowRecord(this.tdoc)) this.response.redirect = this.url('record_detail', { rid });
|
||||
if (contest.canShowRecord.call(this, this.tdoc)) this.response.redirect = this.url('record_detail', { rid });
|
||||
else this.response.redirect = this.url('homework_detail', { tid });
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkCreateHandler extends HomeworkHandler {
|
||||
class HomeworkCreateHandler extends Handler {
|
||||
async get() {
|
||||
const beginAt = moment().add(1, 'day');
|
||||
const penaltySince = beginAt.clone().add(7, 'days');
|
||||
@@ -255,7 +257,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
||||
const endAt = penaltySince.clone().add(extensionDays, 'days');
|
||||
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
|
||||
if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays');
|
||||
const verified = await this.verifyProblems(domainId, pids);
|
||||
const verified = await contest.verifyProblems(domainId, pids);
|
||||
const tid = await contest.add(domainId, title, content, this.user._id,
|
||||
'homework', beginAt.toDate(), endAt.toDate(), verified, rated,
|
||||
{ penaltySince: penaltySince.toDate(), penaltyRules }, document.TYPE_HOMEWORK);
|
||||
@@ -264,7 +266,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkEditHandler extends HomeworkHandler {
|
||||
class HomeworkEditHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
@@ -316,7 +318,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
return i;
|
||||
});
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
|
||||
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
|
||||
else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF);
|
||||
let beginAt;
|
||||
let penaltySince;
|
||||
@@ -332,7 +334,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
}
|
||||
let endAt = penaltySince.clone().add(extensionDays, 'days');
|
||||
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
|
||||
const verified = await this.verifyProblems(domainId, pids);
|
||||
const verified = await contest.verifyProblems(domainId, pids);
|
||||
beginAt = beginAt.toDate();
|
||||
endAt = endAt.toDate();
|
||||
penaltySince = penaltySince.toDate();
|
||||
@@ -350,11 +352,12 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkScoreboardHandler extends HomeworkHandler {
|
||||
class HomeworkScoreboardHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const [tdoc, rows, udict] = await this.getScoreboard(
|
||||
domainId, tid, false, document.TYPE_HOMEWORK,
|
||||
@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 path = [
|
||||
['Hydro', 'homepage'],
|
||||
@@ -369,7 +372,7 @@ class HomeworkScoreboardHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||
class HomeworkScoreboardDownloadHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.Name)
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
@@ -378,12 +381,12 @@ class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||
};
|
||||
if (!getContent[ext]) throw new ValidationError('ext');
|
||||
const [tdoc, rows] = await this.getScoreboard(domainId, tid, true, document.TYPE_HOMEWORK);
|
||||
const [tdoc, rows] = await contest.getScoreboard(domainId, tid, true, 0, document.TYPE_HOMEWORK);
|
||||
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkCodeHandler extends HomeworkHandler {
|
||||
class HomeworkCodeHandler extends Handler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_READ_RECORD_CODE);
|
||||
|
||||
@@ -14,9 +14,7 @@ import {
|
||||
Route, Handler, Connection, ConnectionHandler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
const RecordHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
class RecordListHandler extends RecordHandler {
|
||||
class RecordListHandler extends Handler {
|
||||
@param('page', Types.PositiveInt, true)
|
||||
@param('pid', Types.Name, true)
|
||||
@param('tid', Types.ObjectID, true)
|
||||
@@ -28,7 +26,7 @@ class RecordListHandler extends RecordHandler {
|
||||
if (tid) {
|
||||
const tdoc = await contest.get(domainId, tid, -1);
|
||||
if (!tdoc) throw new ContestNotFoundError(domainId, pid);
|
||||
if (!this.canShowScoreboard(tdoc, true)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
if (!contest.canShowScoreboard.call(this, tdoc, true)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
if (uidOrName) {
|
||||
let udoc = await user.getById(domainId, +uidOrName);
|
||||
@@ -55,7 +53,7 @@ class RecordListHandler extends RecordHandler {
|
||||
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
|
||||
canViewProblem
|
||||
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewProblemHidden, false)
|
||||
: Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, problem.create(rdoc.pid, rdoc.pid)])]),
|
||||
: Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, problem.create(rdoc.pid, rdoc.pid.toString())])]),
|
||||
]);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
@@ -80,7 +78,7 @@ class RecordListHandler extends RecordHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class RecordDetailHandler extends RecordHandler {
|
||||
class RecordDetailHandler extends Handler {
|
||||
@param('rid', Types.ObjectID)
|
||||
async get(domainId: string, rid: ObjectID) {
|
||||
this.response.template = 'record_detail.html';
|
||||
@@ -88,7 +86,7 @@ class RecordDetailHandler extends RecordHandler {
|
||||
if (!rdoc) throw new RecordNotFoundError(rid);
|
||||
if (rdoc.contest) {
|
||||
const tdoc = await contest.get(domainId, rdoc.contest.tid, rdoc.contest.type);
|
||||
if (!this.canShowRecord(tdoc, true)) throw new PermissionError(rid);
|
||||
if (!contest.canShowRecord.call(this, tdoc, true)) throw new PermissionError(rid);
|
||||
}
|
||||
if (rdoc.uid !== this.user._id && !this.user.hasPerm(PERM.PERM_READ_RECORD_CODE)) rdoc.code = null;
|
||||
// eslint-disable-next-line prefer-const
|
||||
@@ -148,16 +146,15 @@ class RecordDetailHandler extends RecordHandler {
|
||||
}
|
||||
}
|
||||
|
||||
const RecordConnectionHandler = contest.ContestHandlerMixin(ConnectionHandler);
|
||||
|
||||
class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
class RecordMainConnectionHandler extends ConnectionHandler {
|
||||
dispose: bus.Disposable;
|
||||
tid: string;
|
||||
|
||||
@param('tid', Types.ObjectID, true)
|
||||
async prepare(domainId: string, tid?: ObjectID) {
|
||||
if (tid) {
|
||||
const tdoc = await contest.get(domainId, tid, -1);
|
||||
if (this.canShowRecord(tdoc)) this.tid = tid.toHexString();
|
||||
if (contest.canShowRecord.call(this, tdoc)) this.tid = tid.toHexString();
|
||||
else throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
|
||||
@@ -192,15 +189,16 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) {
|
||||
class RecordDetailConnectionHandler extends ConnectionHandler {
|
||||
dispose: bus.Disposable;
|
||||
rid: string;
|
||||
|
||||
@param('rid', Types.ObjectID)
|
||||
async prepare(domainId: string, rid: ObjectID) {
|
||||
const rdoc = await record.get(domainId, rid);
|
||||
if (rdoc.contest) {
|
||||
const tdoc = await contest.get(domainId, rdoc.contest.tid, -1);
|
||||
if (!this.canShowRecord(tdoc)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
if (!contest.canShowRecord.call(this, tdoc)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
this.rid = rid.toString();
|
||||
this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
|
||||
|
||||
@@ -84,7 +84,7 @@ class TrainingMainHandler extends Handler {
|
||||
tdict = await training.getList(domainId, Array.from(enrolledTids));
|
||||
}
|
||||
}
|
||||
for (const tdoc of tdocs) tdict[tdoc.docId] = tdoc;
|
||||
for (const tdoc of tdocs) tdict[tdoc.docId.toHexString()] = tdoc;
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['training_main', null],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Readable, Writable } from 'stream';
|
||||
import type { ObjectID } from 'mongodb';
|
||||
import type { Cursor, ObjectID } from 'mongodb';
|
||||
import type fs from 'fs';
|
||||
import type { Dictionary, NumericDictionary } from 'lodash';
|
||||
import type * as Koa from 'koa';
|
||||
@@ -400,17 +400,17 @@ export interface ContestStat extends Record<string, any> {
|
||||
}
|
||||
|
||||
export interface ContestRule {
|
||||
TEXT: string,
|
||||
check: (args: any) => any,
|
||||
statusSort: any,
|
||||
showScoreboard: (tdoc: Tdoc<30 | 60>, now: Date) => boolean,
|
||||
showRecord: (tdoc: Tdoc<30 | 60>, now: Date) => boolean,
|
||||
stat: (tdoc: Tdoc<30 | 60>, journal: any[]) => ContestStat,
|
||||
TEXT: string;
|
||||
check: (args: any) => any;
|
||||
statusSort: any;
|
||||
showScoreboard: (tdoc: Tdoc<30 | 60>, now: Date) => boolean;
|
||||
showRecord: (tdoc: Tdoc<30 | 60>, now: Date) => boolean;
|
||||
stat: (tdoc: Tdoc<30 | 60>, journal: any[]) => ContestStat;
|
||||
scoreboard: (
|
||||
isExport: boolean, _: (s: string) => string,
|
||||
tdoc: Tdoc<30 | 60>, rankedTsdocs: any[], udict: Udict, pdict: Pdict
|
||||
) => ScoreboardRow[],
|
||||
rank: (tsdocs: any[]) => any[],
|
||||
tdoc: Tdoc<30 | 60>, pdict: Pdict, cursor: Cursor<any>, page: number,
|
||||
) => Promise<[board: ScoreboardRow[], udict: Udict]>;
|
||||
ranked: (tdoc: Tdoc<30 | 60>, cursor: Cursor<any>) => Promise<any[]>;
|
||||
}
|
||||
|
||||
export type ContestRules = Dictionary<ContestRule>;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Cursor } from 'mongodb';
|
||||
import { ValidationError } from '../error';
|
||||
|
||||
async function paginate(
|
||||
cursor: Cursor, page: number, pageSize: number,
|
||||
): Promise<[any[]/* docs */, number/* numPages */, number/* count */]> {
|
||||
async function paginate<T>(
|
||||
cursor: Cursor<T>, page: number, pageSize: number,
|
||||
): Promise<[docs: T[], numPages: number, count: number]> {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
const [count, pageDocs] = await Promise.all([
|
||||
cursor.count(),
|
||||
|
||||
@@ -1,16 +1,55 @@
|
||||
function ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): [number, T][] {
|
||||
import { Cursor } from 'mongodb';
|
||||
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][]>;
|
||||
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][]>;
|
||||
}
|
||||
|
||||
const ranked: ranked = async function rankedPage(cursor, equ = (a, b) => a === b, page) {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
const results: [number, T][] = [];
|
||||
for (const doc of diter) {
|
||||
let r = (page - 1) * 100;
|
||||
let count = r;
|
||||
const results = [];
|
||||
const [docs] = await paginate(cursor, page, 100);
|
||||
for (const doc of docs) {
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
results.push([r, doc]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
};
|
||||
ranked.iterate = async function rankedIterate<T>(cursor: Cursor<T>, equ = (a, b) => a === b) {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
async function getNext(): G<T> {
|
||||
if (!await cursor.hasNext()) return [];
|
||||
const doc = await cursor.next();
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
return [[r, doc], getNext];
|
||||
}
|
||||
return await getNext();
|
||||
};
|
||||
ranked.all = async function rankedAll(cursor, equ = (a, b) => a === b) {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
const results = [];
|
||||
const docs = await cursor.toArray();
|
||||
for (const doc of docs) {
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
results.push([r, doc]);
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
global.Hydro.lib.rank = ranked;
|
||||
export = ranked;
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
ContestNotAttendedError, ContestScoreboardHiddenError,
|
||||
} from '../error';
|
||||
import {
|
||||
ContestRule, ContestRules, ScoreboardNode, ScoreboardRow, Tdoc, Udict,
|
||||
ContestRule, ContestRules, Pdict,
|
||||
ScoreboardNode, ScoreboardRow, Tdoc,
|
||||
Udict,
|
||||
} from '../interface';
|
||||
import * as misc from '../lib/misc';
|
||||
import ranked from '../lib/rank';
|
||||
@@ -50,7 +52,10 @@ const acm: ContestRule = {
|
||||
}
|
||||
return { accept, time, detail };
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = 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 = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
@@ -93,7 +98,7 @@ const acm: ContestRule = {
|
||||
for (const item of tsdoc.detail || []) tsddict[item.pid] = item;
|
||||
const row: ScoreboardRow = [];
|
||||
row.push(
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'string', value: rank.toString() },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
|
||||
{ type: 'string', value: tsdoc.accept || 0 },
|
||||
);
|
||||
@@ -134,9 +139,11 @@ const acm: ContestRule = {
|
||||
row.raw = tsdoc;
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
return [rows, udict];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
},
|
||||
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||
};
|
||||
|
||||
const oi: ContestRule = {
|
||||
@@ -154,7 +161,10 @@ const oi: ContestRule = {
|
||||
},
|
||||
showScoreboard: (tdoc, now) => now > tdoc.endAt,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = 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[] = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
@@ -195,9 +205,11 @@ const oi: ContestRule = {
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
return [rows, udict];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
},
|
||||
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||
};
|
||||
|
||||
const ioi: ContestRule = {
|
||||
@@ -255,7 +267,10 @@ const homework: ContestRule = {
|
||||
},
|
||||
showScoreboard: () => true,
|
||||
showRecord: () => true,
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
async scoreboard(isExport, _, tdoc, pdict, cursor, page) {
|
||||
const rankedTsdocs = 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[] = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
@@ -343,9 +358,11 @@ const homework: ContestRule = {
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
return [rows, udict];
|
||||
},
|
||||
async ranked(tdoc, cursor) {
|
||||
return await ranked.all(cursor, (a, b) => a.score === b.score);
|
||||
},
|
||||
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||
};
|
||||
|
||||
export const RULES: ContestRules = {
|
||||
@@ -483,62 +500,6 @@ export function isExtended(tdoc: Tdoc) {
|
||||
return tdoc.penaltySince.getTime() <= now && now < tdoc.endAt.getTime();
|
||||
}
|
||||
|
||||
export const ContestHandlerMixin = (c) => class extends c {
|
||||
canViewHiddenScoreboard() {
|
||||
return this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
|
||||
canShowRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async getScoreboard(
|
||||
domainId: string, tid: ObjectID,
|
||||
isExport = false, docType: 30 | 60 = document.TYPE_CONTEST,
|
||||
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict]> {
|
||||
const {
|
||||
tdoc, udict, pdict, rankedTsdocs,
|
||||
} = await this.getRawStatus(domainId, tid, docType);
|
||||
const rows = RULES[tdoc.rule].scoreboard(
|
||||
isExport, this.translate.bind(this),
|
||||
tdoc, rankedTsdocs, udict, pdict,
|
||||
);
|
||||
return [tdoc, rows, udict];
|
||||
}
|
||||
|
||||
async getRawStatus(domainId: string, tid: ObjectID, docType: 30 | 60 = document.TYPE_CONTEST) {
|
||||
const tdoc = await get(domainId, tid, docType);
|
||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
if (!this.canShowRecord(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort).toArray();
|
||||
const uids = tsdocs.map((tsdoc) => tsdoc.uid);
|
||||
const [udict, pdict] = await Promise.all([
|
||||
user.getList(domainId, uids),
|
||||
problem.getList(domainId, tdoc.pids, true),
|
||||
]);
|
||||
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
|
||||
return {
|
||||
tdoc, tsdocs, udict, pdict, rankedTsdocs,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
async verifyProblems(domainId: string, pids: Array<number | string>) {
|
||||
const pdict = await problem.getList(domainId, pids, true);
|
||||
const _pids: Set<number> = new Set();
|
||||
for (const i in pdict) _pids.add(pdict[i].docId);
|
||||
return Array.from(_pids);
|
||||
}
|
||||
};
|
||||
|
||||
export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: any) {
|
||||
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
|
||||
}
|
||||
@@ -584,6 +545,45 @@ export async function recalcStatus(domainId: string, tid: ObjectID, type: Type =
|
||||
return await Promise.all(tasks);
|
||||
}
|
||||
|
||||
export function canViewHiddenScoreboard() {
|
||||
return this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
|
||||
export function canShowRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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]> {
|
||||
const tdoc = await get(domainId, tid, docType);
|
||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
if (!this.canShowRecord(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(
|
||||
isExport, this.translate.bind(this),
|
||||
tdoc, pdict, tsdocs, page,
|
||||
);
|
||||
return [tdoc, rows, udict, pdict];
|
||||
}
|
||||
|
||||
export async function verifyProblems(domainId: string, pids: Array<number | string>) {
|
||||
const pdict = await problem.getList(domainId, pids, true);
|
||||
const _pids: Set<number> = new Set();
|
||||
for (const i in pdict) _pids.add(pdict[i].docId);
|
||||
return Array.from(_pids);
|
||||
}
|
||||
|
||||
export const statusText = (tdoc: Tdoc) => (
|
||||
isNew(tdoc)
|
||||
? 'New'
|
||||
@@ -602,7 +602,6 @@ export const getStatusText = (tdoc: Tdoc) => (
|
||||
|
||||
global.Hydro.model.contest = {
|
||||
RULES,
|
||||
ContestHandlerMixin,
|
||||
add,
|
||||
getListStatus,
|
||||
getMultiStatus,
|
||||
@@ -617,6 +616,11 @@ global.Hydro.model.contest = {
|
||||
setStatus,
|
||||
getAndListStatus,
|
||||
recalcStatus,
|
||||
canShowRecord,
|
||||
canShowScoreboard,
|
||||
canViewHiddenScoreboard,
|
||||
getScoreboard,
|
||||
verifyProblems,
|
||||
isNew,
|
||||
isUpcoming,
|
||||
isNotStarted,
|
||||
|
||||
@@ -94,17 +94,18 @@ export async function add(
|
||||
}
|
||||
|
||||
// FIXME typings doesn't work
|
||||
export function get<T extends Ddoc.Field>(domainId: string, did: ObjectID, projection: T[] = PROJECTION_PUBLIC as any): Promise<Pick<Ddoc, T>> {
|
||||
return document.get(domainId, document.TYPE_DISCUSSION, did, projection);
|
||||
export async function get<T extends Ddoc.Field>(domainId: string, did: ObjectID, projection: T[] = PROJECTION_PUBLIC as any): Promise<Pick<Ddoc, T>> {
|
||||
return await document.get(domainId, document.TYPE_DISCUSSION, did, projection);
|
||||
}
|
||||
|
||||
export function edit(
|
||||
domainId: string, did: ObjectID,
|
||||
title: string, content: string, highlight: boolean, pin: boolean,
|
||||
): Promise<Ddoc | null> {
|
||||
return document.set(domainId, document.TYPE_DISCUSSION, did, {
|
||||
const payload = {
|
||||
title, content, highlight, pin,
|
||||
});
|
||||
};
|
||||
return document.set(domainId, document.TYPE_DISCUSSION, did, payload);
|
||||
}
|
||||
|
||||
export function del(domainId: string, did: ObjectID): Promise<never> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Collection } from 'mongodb';
|
||||
import LRU from 'lru-cache';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import * as system from './system';
|
||||
import token from './token';
|
||||
@@ -16,6 +17,16 @@ 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);
|
||||
|
||||
function deleteUserCache(udoc: User | Udoc) {
|
||||
if (udoc) {
|
||||
const id = [udoc._id.toString(), udoc.uname.toLowerCase(), udoc.mail.toLowerCase()];
|
||||
for (const key of cache.keys().filter((k) => id.includes(k.split('/')[0]))) {
|
||||
cache.del(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class User implements _User {
|
||||
_id: number;
|
||||
@@ -125,13 +136,18 @@ class UserModel {
|
||||
|
||||
@ArgMethod
|
||||
static async getById(domainId: string, _id: number, scope: bigint | string = PERM.PERM_ALL): Promise<User | null> {
|
||||
if (cache.has(`${_id}/${domainId}`)) return cache.get(`${_id}/${domainId}`);
|
||||
const udoc = _id === 0 || _id === 1
|
||||
? BUILTIN_USERS[_id]
|
||||
: await coll.findOne({ _id });
|
||||
if (!udoc) return null;
|
||||
const dudoc = await domain.getDomainUser(domainId, udoc);
|
||||
if (typeof scope === 'string') scope = BigInt(scope);
|
||||
return new UserModel.User(udoc, dudoc, scope).init();
|
||||
const res = await new User(udoc, dudoc, scope).init();
|
||||
cache.set(`${res._id}/${domainId}`, res);
|
||||
cache.set(`${res.uname.toLowerCase()}/${domainId}`, res);
|
||||
cache.set(`${res.mail.toLowerCase()}/${domainId}`, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static async getList(domainId: string, uids: number[]): Promise<Udict> {
|
||||
@@ -145,6 +161,7 @@ class UserModel {
|
||||
@ArgMethod
|
||||
static async getByUname(domainId: string, uname: string): Promise<User | null> {
|
||||
const unameLower = uname.trim().toLowerCase();
|
||||
if (cache.has(`${unameLower}/${domainId}`)) return cache.get(`${unameLower}/${domainId}`);
|
||||
const udoc = (unameLower === 'guest')
|
||||
? BUILTIN_USERS[0]
|
||||
: unameLower === 'hydro'
|
||||
@@ -152,12 +169,17 @@ class UserModel {
|
||||
: await coll.findOne({ unameLower });
|
||||
if (!udoc) return null;
|
||||
const dudoc = await domain.getDomainUser(domainId, udoc);
|
||||
return new UserModel.User(udoc, dudoc).init();
|
||||
const res = await new UserModel.User(udoc, dudoc).init();
|
||||
cache.set(`${res._id}/${domainId}`, res);
|
||||
cache.set(`${res.uname.toLowerCase()}/${domainId}`, res);
|
||||
cache.set(`${res.mail.toLowerCase()}/${domainId}`, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
static async getByEmail(domainId: string, mail: string): Promise<User | null> {
|
||||
const mailLower = mail.trim().toLowerCase();
|
||||
if (cache.has(`${mailLower}/${domainId}`)) return cache.get(`${mailLower}/${domainId}`);
|
||||
const udoc = (mailLower === 'guest@hydro.local')
|
||||
? BUILTIN_USERS[0]
|
||||
: mailLower === 'hydro@hydro.local'
|
||||
@@ -165,7 +187,11 @@ class UserModel {
|
||||
: await coll.findOne({ mailLower });
|
||||
if (!udoc) return null;
|
||||
const dudoc = await domain.getDomainUser(domainId, udoc);
|
||||
return new UserModel.User(udoc, dudoc).init();
|
||||
const res = await new UserModel.User(udoc, dudoc).init();
|
||||
cache.set(`${res._id}/${domainId}`, res);
|
||||
cache.set(`${res.uname.toLowerCase()}/${domainId}`, res);
|
||||
cache.set(`${res.mail.toLowerCase()}/${domainId}`, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
@@ -173,7 +199,9 @@ class UserModel {
|
||||
const op: any = {};
|
||||
if ($set && Object.keys($set).length) op.$set = $set;
|
||||
if ($unset && Object.keys($unset).length) op.$unset = $unset;
|
||||
return await coll.findOneAndUpdate({ _id: uid }, op);
|
||||
const res = await coll.findOneAndUpdate({ _id: uid }, op, { returnOriginal: false });
|
||||
deleteUserCache(res.value);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
@@ -189,6 +217,7 @@ class UserModel {
|
||||
{ $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } },
|
||||
{ returnOriginal: false },
|
||||
);
|
||||
deleteUserCache(res.value);
|
||||
return res.value;
|
||||
}
|
||||
|
||||
@@ -198,6 +227,7 @@ class UserModel {
|
||||
if (!udoc) throw new UserNotFoundError(_id);
|
||||
udoc[field] = udoc[field] + n || n;
|
||||
await coll.updateOne({ _id }, { $set: { [field]: udoc[field] } });
|
||||
deleteUserCache(udoc);
|
||||
return udoc;
|
||||
}
|
||||
|
||||
@@ -252,12 +282,13 @@ class UserModel {
|
||||
|
||||
@ArgMethod
|
||||
static async setPriv(uid: number, priv: number): Promise<Udoc | null> {
|
||||
const udoc = await coll.findOneAndUpdate(
|
||||
const res = await coll.findOneAndUpdate(
|
||||
{ _id: uid },
|
||||
{ $set: { priv } },
|
||||
{ returnOriginal: false },
|
||||
);
|
||||
return udoc.value;
|
||||
deleteUserCache(res.value);
|
||||
return res.value;
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
|
||||
@@ -42,8 +42,8 @@ async function runProblem(...arg: any[]) {
|
||||
);
|
||||
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
|
||||
for (const psdoc of psdocs) {
|
||||
if (rdict[psdoc.rid]) {
|
||||
const rp = rdict[psdoc.rid].score * p;
|
||||
if (rdict[psdoc.rid.toHexString()]) {
|
||||
const rp = rdict[psdoc.rid.toHexString()].score * p;
|
||||
udict[psdoc.uid] = (udict[psdoc.uid] || 1500) + rp;
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,10 @@ async function runContest(...arg: any[]) {
|
||||
: arg[0];
|
||||
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
|
||||
const report = (typeof arg[0] === 'string') ? arg[3] : arg[2];
|
||||
const tsdocs = await contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType)
|
||||
.sort(contest.RULES[tdoc.rule].statusSort).toArray();
|
||||
if (!tsdocs.length) return;
|
||||
const rankedTsdocs = contest.RULES[tdoc.rule].rank(tsdocs);
|
||||
const cursor = await contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType)
|
||||
.sort(contest.RULES[tdoc.rule].statusSort);
|
||||
if (!await cursor.count()) return;
|
||||
const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor);
|
||||
const users = [];
|
||||
for (const result of rankedTsdocs) {
|
||||
users.push({ uid: result[1].uid, rank: result[0], old: udict[result[1].uid] || 1500 });
|
||||
|
||||
Reference in new issue
Block a user