core: database structure update

This commit is contained in:
undefined committed 2021-09-23 18:15:17 +08:00
1 parent dca8e28361
commit 4daf8bac80
24 files changed
+182 -263

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.38.0",
"version": "2.39.0",
"bin": "bin/hydrooj.js",
"main": "src/loader",
"module": "src/loader",
@@ -36,7 +36,7 @@
"moment-timezone": "^0.5.33",
"mongodb": "^3.7.1",
"nanoid": "^3.1.25",
"nodemailer": "^6.6.3",
"nodemailer": "^6.6.4",
"p-queue": "^7.1.0",
"reflect-metadata": "^0.1.13",
"serialize-javascript": "^6.0.0",
+6 -19
View File
@@ -15,7 +15,6 @@ import {
import paginate from '../lib/paginate';
import { PERM, PRIV } from '../model/builtin';
import * as contest from '../model/contest';
import * as document from '../model/document';
import domain from '../model/domain';
import message from '../model/message';
import problem from '../model/problem';
@@ -32,19 +31,15 @@ export class ContestListHandler extends Handler {
@param('rule', Types.Range(contest.RULES), true)
@param('page', Types.PositiveInt, true)
async get(domainId: string, rule = '', page = 1) {
const cursor = contest.getMulti(domainId, rule ? { rule } : undefined);
const cursor = contest.getMulti(domainId, rule ? { rule } : { rule: { $ne: 'homework' } });
const qs = rule ? `rule=${rule}` : '';
const [tdocs, tpcount] = await paginate(cursor, page, system.get('pagination.contest'));
const tids = [];
for (const tdoc of tdocs) tids.push(tdoc.docId);
const tsdict = await contest.getListStatus(domainId, this.user._id, tids);
const path = [
['Hydro', 'homepage'],
['contest_main', null],
];
this.response.template = 'contest_main.html';
this.response.body = {
page, tpcount, qs, rule, tdocs, tsdict, path,
page, tpcount, qs, rule, tdocs, tsdict,
};
}
}
@@ -68,17 +63,12 @@ export class ContestDetailHandler extends Handler {
if (contest.canShowSelfRecord.call(this, tdoc)) {
const q = [];
for (const i in psdict) q.push(psdict[i].rid);
rdict = await record.getList(domainId, q, true);
rdict = await record.getList(domainId, q);
} else {
for (const i in psdict) rdict[psdict[i].rid] = { _id: psdict[i].rid };
}
} else attended = false;
const udict = await user.getList(domainId, [tdoc.owner]);
const path = [
['Hydro', 'homepage'],
['contest_main', 'contest_main'],
[tdoc.title, null, null, true],
];
const index = tdoc.pids.map((i) => i.toString());
for (const key in pdict) {
pdict[key].tag.length = 0;
@@ -86,7 +76,7 @@ export class ContestDetailHandler extends Handler {
if (i !== '9') pdict[key].pid = i;
}
this.response.body = {
path, tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page,
tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page,
};
}
@@ -258,7 +248,7 @@ export class ContestEditHandler extends Handler {
}
export class ContestProblemHandler extends Handler {
tdoc: Tdoc<30 | 60>;
tdoc: Tdoc<30>;
pdoc: ProblemDoc;
tsdoc: any;
udoc: User;
@@ -391,10 +381,7 @@ export class ContestDetailProblemSubmitHandler extends ContestProblemHandler {
throw new BadRequestError('Language not allowed.');
}
await this.limitRate('add_record', 60, system.get('limit.submission'));
const rid = await record.add(domainId, pid, this.user._id, lang, code, true, pretest ? input : {
type: document.TYPE_CONTEST,
tid,
});
const rid = await record.add(domainId, pid, this.user._id, lang, code, true, pretest ? input : tid);
const rdoc = await record.get(domainId, rid);
if (!rdoc) throw new RecordNotFoundError(domainId, rid);
if (!pretest) {
+1 -1
View File
@@ -19,7 +19,7 @@ export const typeMapper = {
contest: document.TYPE_CONTEST,
node: document.TYPE_DISCUSSION_NODE,
training: document.TYPE_TRAINING,
homework: document.TYPE_HOMEWORK,
homework: document.TYPE_CONTEST,
};
class DiscussionHandler extends Handler {
+1 -3
View File
@@ -8,9 +8,7 @@ import {
import type { DomainDoc } from '../interface';
import avatar from '../lib/avatar';
import paginate from '../lib/paginate';
import {
PERM, PERMS_BY_FAMILY, PRIV,
} from '../model/builtin';
import { PERM, PERMS_BY_FAMILY, PRIV } from '../model/builtin';
import * as discussion from '../model/discussion';
import domain from '../model/domain';
import { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } from '../model/setting';
+3 -5
View File
@@ -14,7 +14,6 @@ import BlackListModel from '../model/blacklist';
import { PERM, PRIV } from '../model/builtin';
import * as contest from '../model/contest';
import * as discussion from '../model/discussion';
import * as document from '../model/document';
import domain from '../model/domain';
import message from '../model/message';
import ProblemModel from '../model/problem';
@@ -39,11 +38,10 @@ class HomeHandler extends Handler {
async getHomework(domainId: string, limit = 5) {
if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) {
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK)
const tdocs = await contest.getMulti(domainId, { rule: 'homework' })
.limit(limit).toArray();
const tsdict = await contest.getListStatus(
domainId, this.user._id,
tdocs.map((tdoc) => tdoc.docId), document.TYPE_HOMEWORK,
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
);
return ['homework', tdocs, tsdict];
}
@@ -52,7 +50,7 @@ class HomeHandler extends Handler {
async getContest(domainId: string, limit = 10) {
if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) {
const tdocs = await contest.getMulti(domainId)
const tdocs = await contest.getMulti(domainId, { rule: { $ne: 'homework' } })
.limit(limit).toArray();
const tsdict = await contest.getListStatus(
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
+38 -72
View File
@@ -6,17 +6,16 @@ import moment from 'moment-timezone';
import { ObjectID } from 'mongodb';
import { Time } from '@hydrooj/utils/lib/utils';
import {
BadRequestError, HomeworkNotAttendedError, HomeworkNotLiveError,
ProblemNotFoundError, ValidationError,
BadRequestError, ContestNotFoundError, HomeworkNotAttendedError,
HomeworkNotLiveError, ProblemNotFoundError, ValidationError,
} from '../error';
import {
DomainDoc, PenaltyRules, ProblemDoc, Tdoc, User,
DomainDoc, PenaltyRules, ProblemDoc, ProblemId, Tdoc, User,
} from '../interface';
import paginate from '../lib/paginate';
import { PERM, PRIV, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import * as discussion from '../model/discussion';
import * as document from '../model/document';
import domain from '../model/domain';
import problem from '../model/problem';
import record from '../model/record';
@@ -33,7 +32,7 @@ const convertPenaltyRules = validatePenaltyRules;
class HomeworkMainHandler extends Handler {
async get({ domainId }) {
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK).toArray();
const tdocs = await contest.getMulti(domainId, { rule: 'homework' }).toArray();
const calendar = [];
for (const tdoc of tdocs) {
const cal = { ...tdoc, url: this.url('homework_detail', { tid: tdoc.docId }) };
@@ -43,11 +42,7 @@ class HomeworkMainHandler extends Handler {
} else cal.endAt = tdoc.penaltySince;
calendar.push(cal);
}
const path = [
['Hydro', 'homepage'],
['homework_main', null],
];
this.response.body = { tdocs, calendar, path };
this.response.body = { tdocs, calendar };
this.response.template = 'homework_main.html';
}
}
@@ -56,9 +51,10 @@ class HomeworkDetailHandler extends Handler {
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
const tdoc = await contest.get(domainId, tid);
if (tdoc.rule !== 'homework') throw new ContestNotFoundError(domainId, tid);
const [tsdoc, pdict] = await Promise.all([
contest.getStatus(domainId, tdoc.docId, this.user._id, document.TYPE_HOMEWORK),
contest.getStatus(domainId, tdoc.docId, this.user._id),
problem.getList(domainId, tdoc.pids, true),
]);
const psdict = {};
@@ -74,7 +70,6 @@ class HomeworkDetailHandler extends Handler {
rdict = await record.getList(
domainId,
tsdoc.journal.map((pdetail) => pdetail.rid),
true,
);
}
}
@@ -87,11 +82,6 @@ class HomeworkDetailHandler extends Handler {
const uids = ddocs.map((ddoc) => ddoc.owner);
uids.push(tdoc.owner);
const udict = await user.getList(domainId, uids);
const path = [
['Hydro', 'homepage'],
['homework_main', 'homework_main'],
[tdoc.title, null, null, true],
];
const index = tdoc.pids.map((i) => i.toString());
for (const key in pdict) {
const i = (index.indexOf(key) + 10).toString(36).toUpperCase();
@@ -99,31 +89,32 @@ class HomeworkDetailHandler extends Handler {
}
this.response.template = 'homework_detail.html';
this.response.body = {
tdoc, tsdoc, attended, udict, pdict, psdict, rdict, ddocs, page, dpcount, dcount, path,
tdoc, tsdoc, attended, udict, pdict, psdict, rdict, ddocs, page, dpcount, dcount,
};
}
@param('tid', Types.ObjectID)
async postAttend(domainId: string, tid: ObjectID) {
this.checkPerm(PERM.PERM_ATTEND_HOMEWORK);
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
const tdoc = await contest.get(domainId, tid);
if (contest.isDone(tdoc)) throw new HomeworkNotLiveError(tdoc.docId);
await contest.attend(domainId, tdoc.docId, this.user._id, document.TYPE_HOMEWORK);
await contest.attend(domainId, tdoc.docId, this.user._id);
this.back();
}
@param('tid', Types.ObjectID)
async postDelete(domainId: string, tid: ObjectID) {
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
const tdoc = await contest.get(domainId, tid);
if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
await contest.del(domainId, tid, document.TYPE_HOMEWORK);
await contest.del(domainId, tid);
this.response.redirect = this.url('homework_main');
}
}
class HomeworkDetailProblemHandler extends Handler {
tdoc: Tdoc<60>;
tdoc: Tdoc<30>;
pdoc: ProblemDoc;
pid: ProblemId;
tsdoc: any;
udoc: User;
attended: boolean;
@@ -131,19 +122,20 @@ class HomeworkDetailProblemHandler extends Handler {
@param('tid', Types.ObjectID)
@param('pid', Types.String)
async _prepare(domainId: string, tid: ObjectID, _pid: string) {
this.tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
const pid = this.tdoc.pids[parseInt(_pid, 36) - 10];
if (!pid) throw new ProblemNotFoundError(domainId, tid, _pid);
this.tdoc = await contest.get(domainId, tid);
if (this.tdoc.rule !== 'homework') throw new ContestNotFoundError(domainId, tid);
this.pid = this.tdoc.pids[parseInt(_pid, 36) - 10];
if (!this.pid) throw new ProblemNotFoundError(domainId, tid, _pid);
[this.udoc, this.pdoc, this.tsdoc] = await Promise.all([
user.getById(domainId, this.tdoc.owner),
problem.get(domainId, pid),
contest.getStatus(domainId, tid, this.user._id, document.TYPE_HOMEWORK),
problem.get(domainId, this.pid),
contest.getStatus(domainId, tid, this.user._id),
]);
if (!this.pdoc) throw new ProblemNotFoundError(domainId, pid);
if (!this.pdoc) throw new ProblemNotFoundError(domainId, this.pid);
// @ts-ignore
if (this.pdoc.domainId !== domainId) this.pdoc.docId = `${this.pdoc.domainId}:${this.pdoc.docId}`;
this.pdoc.pid = _pid;
this.attended = this.tsdoc && this.tsdoc.attend === 1;
this.attended = this.tsdoc?.attend === 1;
this.response.body = {
tdoc: this.tdoc,
pdoc: this.pdoc,
@@ -212,13 +204,11 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
pdomain: DomainDoc;
@param('tid', Types.ObjectID)
@param('pid', Types.Name)
async prepare(domainId: string, tid: ObjectID, _pid: string) {
const pid = this.tdoc.pids[parseInt(_pid, 36) - 10];
async prepare(domainId: string, tid: ObjectID) {
if (!this.attended) throw new HomeworkNotAttendedError(tid);
if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid);
this.pdomainId = typeof pid === 'string' ? pid.split(':')[0] : domainId;
this.ppid = typeof pid === 'number' ? pid : +pid.split(':')[1];
this.pdomainId = typeof this.pid === 'string' ? this.pid.split(':')[0] : domainId;
this.ppid = typeof this.pid === 'number' ? this.pid : +this.pid.split(':')[1];
this.pdomain = await domain.get(this.pdomainId);
if (this.pdomain.langs) {
this.response.body.pdoc.config.langs = intersection(
@@ -243,29 +233,23 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
}
@param('tid', Types.ObjectID)
@param('pid', Types.String)
@param('code', Types.Content)
@param('lang', Types.Name)
@param('pretest', Types.Boolean)
@param('input', Types.String, true)
async post(domainId: string, tid: ObjectID, _pid: string, code: string, lang: string, pretest = false, input = '') {
const pid = this.tdoc.pids[parseInt(_pid, 36) - 10];
async post(domainId: string, tid: ObjectID, code: string, lang: string, pretest = false, input = '') {
if (this.response.body.pdoc.config?.langs && !this.response.body.pdoc.config.langs.includes(lang)) {
throw new BadRequestError('Language not allowed.');
}
await this.limitRate('add_record', 60, system.get('limit.submission'));
const rid = await record.add(domainId, pid, this.user._id, lang, code, true, pretest ? input : {
type: document.TYPE_HOMEWORK,
tid,
});
const rid = await record.add(domainId, this.pid, this.user._id, lang, code, true, pretest ? input : tid);
const rdoc = await record.get(domainId, rid);
if (!pretest) {
await Promise.all([
(this.tsdoc.journal || []).filter((i) => i.pid === pid).length
(this.tsdoc.journal || []).filter((i) => i.pid === this.pid).length
&& problem.inc(this.pdomainId, this.ppid, 'nSubmit', 1),
domain.incUserInDomain(domainId, this.user._id, 'nSubmit'),
contest.updateStatus(domainId, tid, this.user._id,
rid, pid, STATUS.STATUS_WAITING, 0, document.TYPE_HOMEWORK),
contest.updateStatus(domainId, tid, this.user._id, rid, this.pid, STATUS.STATUS_WAITING),
]);
}
bus.broadcast('record/change', rdoc);
@@ -279,9 +263,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
class HomeworkEditHandler extends Handler {
@param('tid', Types.ObjectID, true)
async get(domainId: string, tid: ObjectID) {
const tdoc = tid
? await contest.get(domainId, tid, document.TYPE_HOMEWORK)
: null;
const tdoc = tid ? await contest.get(domainId, tid) : null;
if (!tid) this.checkPerm(PERM.PERM_CREATE_HOMEWORK);
else if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF);
@@ -290,16 +272,6 @@ class HomeworkEditHandler extends Handler {
(tdoc.endAt.getTime() - tdoc.penaltySince.getTime()) / (Time.day / 100),
) / 100
: 1;
const path = [
['Hydro', 'homepage'],
['homework_main', 'homework_main'],
...tid
? [
[tdoc.title, 'homework_detail', { tid }, true],
['homework_edit', null],
]
: [['homework_create', null]],
];
const beginAt = tid
? moment(tdoc.beginAt).tz(this.user.timeZone)
: moment().add(1, 'day').tz(this.user.timeZone).hour(0).minute(0).millisecond(0);
@@ -316,7 +288,6 @@ class HomeworkEditHandler extends Handler {
extensionDays,
penaltyRules: tid ? yaml.dump(tdoc.penaltyRules) : null,
pids: tid ? tdoc.pids.join(',') : '',
path,
page_name: tid ? 'homework_edit' : 'homework_create',
};
}
@@ -344,9 +315,7 @@ class HomeworkEditHandler extends Handler {
if (!i.includes(':')) throw new ValidationError('pids');
return i;
}).filter((i) => i);
const tdoc = tid
? await contest.get(domainId, tid, document.TYPE_HOMEWORK)
: null;
const tdoc = tid ? await contest.get(domainId, tid) : null;
if (!tid) this.checkPerm(PERM.PERM_CREATE_HOMEWORK);
else if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF);
@@ -361,16 +330,16 @@ class HomeworkEditHandler extends Handler {
if (!tid) {
tid = await contest.add(domainId, title, content, this.user._id,
'homework', beginAt.toDate(), endAt.toDate(), pids, rated,
{ penaltySince: penaltySince.toDate(), penaltyRules }, document.TYPE_HOMEWORK);
{ penaltySince: penaltySince.toDate(), penaltyRules });
} else {
await contest.edit(domainId, tid, {
title, content, beginAt: beginAt.toDate(), endAt: endAt.toDate(), pids, penaltySince: penaltySince.toDate(), penaltyRules, rated,
}, document.TYPE_HOMEWORK);
});
if (tdoc.beginAt !== beginAt.toDate()
|| tdoc.endAt !== endAt.toDate()
|| tdoc.penaltySince !== penaltySince.toDate()
|| tdoc.pids.sort().join(' ') !== pids.sort().join(' ')) {
await contest.recalcStatus(domainId, tdoc.docId, document.TYPE_HOMEWORK);
await contest.recalcStatus(domainId, tdoc.docId);
}
}
this.response.body = { tid };
@@ -383,8 +352,7 @@ class HomeworkScoreboardHandler extends Handler {
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
const [tdoc, rows, udict, , nPages] = await contest.getScoreboard.call(
this, domainId, tid, false,
page, document.TYPE_HOMEWORK,
this, domainId, tid, false, page,
);
const path = [
['Hydro', 'homepage'],
@@ -409,7 +377,7 @@ class HomeworkScoreboardDownloadHandler extends Handler {
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
};
if (!getContent[ext]) throw new ValidationError('ext');
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0, document.TYPE_HOMEWORK);
const [tdoc, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0);
this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`);
}
}
@@ -418,9 +386,7 @@ class HomeworkCodeHandler extends Handler {
@param('tid', Types.ObjectID)
async get(domainId: string, tid: ObjectID) {
await this.limitRate('contest_code', 3600, 60);
const [tdoc, tsdocs] = await contest.getAndListStatus(
domainId, tid, document.TYPE_HOMEWORK,
);
const [tdoc, tsdocs] = await contest.getAndListStatus(domainId, tid);
if (!this.user.own(tdoc) && !this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) {
this.checkPerm(PERM.PERM_READ_RECORD_CODE);
}
+2 -2
View File
@@ -32,9 +32,9 @@ export async function postJudge(rdoc: RecordDoc) {
const updated = await problem.updateStatus(rdoc.pdomain, rdoc.pid, rdoc.uid, rdoc._id, rdoc.status, rdoc.score);
if (rdoc.contest) {
await contest.updateStatus(
rdoc.domainId, rdoc.contest.tid, rdoc.uid, rdoc._id,
rdoc.domainId, rdoc.contest, rdoc.uid, rdoc._id,
rdoc.domainId === rdoc.pdomain ? rdoc.pid : `${rdoc.pdomain}:${rdoc.pid}`,
rdoc.status, rdoc.score, rdoc.contest.type,
rdoc.status, rdoc.score,
);
} else if (accept && updated) await domain.incUserInDomain(rdoc.domainId, rdoc.uid, 'nAccept', 1);
const pdoc = (accept && updated)
+2 -7
View File
@@ -16,7 +16,6 @@ import paginate from '../lib/paginate';
import { isPid, parsePid as convertPid } from '../lib/validator';
import { PERM, PRIV } from '../model/builtin';
import * as contest from '../model/contest';
import * as document from '../model/document';
import domain from '../model/domain';
import problem from '../model/problem';
import record from '../model/record';
@@ -258,12 +257,8 @@ export class ProblemDetailHandler extends ProblemHandler {
async postDelete() {
if (!this.user.own(this.pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
const [ctdocs, htdocs] = await Promise.all([
contest.getRelated(this.domainId, this.pdoc.docId, document.TYPE_CONTEST),
contest.getRelated(this.domainId, this.pdoc.docId, document.TYPE_HOMEWORK),
]);
if (ctdocs.length) throw new BadRequestError('Problem already used by contest {0}', ctdocs[0]._id);
if (htdocs.length) throw new BadRequestError('Problem already used by homwrork {0}', htdocs[0]._id);
const tdocs = await contest.getRelated(this.domainId, this.pdoc.docId);
if (tdocs.length) throw new BadRequestError('Problem already used by contest {0}', tdocs[0]._id);
await problem.del(this.pdoc.domainId, this.pdoc.docId);
this.response.redirect = this.url('problem_main');
}
+18 -15
View File
@@ -4,7 +4,7 @@ import {
ContestNotAttendedError, ContestNotFoundError, PermissionError,
ProblemNotFoundError, RecordNotFoundError, UserNotFoundError,
} from '../error';
import { RecordDoc } from '../interface';
import { RecordDoc, Tdoc } from '../interface';
import { PERM, PRIV, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import problem from '../model/problem';
@@ -34,13 +34,13 @@ class RecordListHandler extends Handler {
let tdoc = null;
let invalid = false;
this.response.template = 'record_main.html';
const q: FilterQuery<RecordDoc> = { 'contest.tid': tid, hidden: false };
const q: FilterQuery<RecordDoc> = { contest: tid };
if (full) uidOrName = this.user._id.toString();
if (tid) {
tdoc = await contest.get(domainId, tid, -1);
tdoc = await contest.get(domainId, tid);
if (!tdoc) throw new ContestNotFoundError(domainId, pid);
if (!contest.canShowScoreboard.call(this, tdoc, true)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
if (!this.user.own(tdoc) && !(await contest.getStatus(domainId, tid, this.user._id, -1))?.attend) {
if (!this.user.own(tdoc) && !(await contest.getStatus(domainId, tid, this.user._id))?.attend) {
throw new ContestNotAttendedError(domainId, tid);
}
}
@@ -108,7 +108,7 @@ class RecordDetailHandler extends Handler {
const rdoc = await record.get(domainId, rid);
if (!rdoc) throw new RecordNotFoundError(rid);
if (rdoc.contest) {
const tdoc = await contest.get(domainId, rdoc.contest.tid, rdoc.contest.type);
const tdoc = await contest.get(domainId, rdoc.contest);
let canView = this.user.own(tdoc);
canView ||= contest.canShowRecord.call(this, tdoc);
canView ||= contest.canShowSelfRecord.call(this, tdoc, true) && rdoc.uid === this.user._id;
@@ -135,7 +135,7 @@ class RecordDetailHandler extends Handler {
}
this.response.body = { udoc, rdoc, pdoc };
if (rdoc.contest) {
this.response.body.tdoc = await contest.get(domainId, rdoc.contest.tid, rdoc.contest.type);
this.response.body.tdoc = await contest.get(domainId, rdoc.contest);
}
}
@@ -182,6 +182,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
pid: number;
status: number;
pretest = false;
tdoc: Tdoc<30>;
@param('tid', Types.ObjectID, true)
@param('pid', Types.Name, true)
@@ -194,9 +195,9 @@ class RecordMainConnectionHandler extends ConnectionHandler {
status?: number, pretest = false, all = false,
) {
if (tid) {
const tdoc = await contest.get(domainId, tid, -1);
if (!tdoc) throw new ContestNotFoundError(domainId, tid);
if (pretest || contest.canShowScoreboard.call(this, tdoc, true)) this.tid = tid.toHexString();
this.tdoc = await contest.get(domainId, tid);
if (!this.tdoc) throw new ContestNotFoundError(domainId, tid);
if (pretest || contest.canShowScoreboard.call(this, this.tdoc, true)) this.tid = tid.toHexString();
else throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
}
if (pretest) {
@@ -236,10 +237,12 @@ class RecordMainConnectionHandler extends ConnectionHandler {
}
async onRecordChange(rdoc: RecordDoc) {
if (!this.all && !this.pretest && rdoc.input) return;
if (!this.all && rdoc.domainId !== this.domainId) return;
if (!this.all && rdoc.contest && rdoc.contest.tid.toString() !== this.tid) return;
if (!this.all && this.pid && rdoc.pid !== this.pid) return;
if (!this.all) {
if (!this.pretest && rdoc.input) return;
if (rdoc.domainId !== this.domainId) return;
if (rdoc.contest && ![this.tid, '000000000000000000000000'].includes(rdoc.contest.toString())) return;
}
if (this.pid && rdoc.pid !== this.pid) return;
if (this.uid && rdoc.uid !== this.uid) return;
// eslint-disable-next-line prefer-const
@@ -247,7 +250,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
user.getById(this.domainId, rdoc.uid),
problem.get(rdoc.pdomain, rdoc.pid),
]);
const tdoc = this.tid ? await contest.get(rdoc.domainId, new ObjectID(this.tid), -1) : null;
const tdoc = this.tid ? this.tdoc || await contest.get(rdoc.domainId, new ObjectID(this.tid)) : null;
if (pdoc && !rdoc.contest) {
if (pdoc.hidden && !this.user.own(pdoc) && !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) pdoc = null;
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null;
@@ -272,7 +275,7 @@ class RecordDetailConnectionHandler extends ConnectionHandler {
const rdoc = await record.get(domainId, rid);
if (!rdoc) return;
if (rdoc.contest && rdoc.input === undefined) {
const tdoc = await contest.get(domainId, rdoc.contest.tid, -1);
const tdoc = await contest.get(domainId, rdoc.contest);
let canView = this.user.own(tdoc);
canView ||= contest.canShowRecord.call(this, tdoc);
canView ||= this.user._id === rdoc.uid && contest.canShowSelfRecord.call(this, tdoc);
+9 -16
View File
@@ -28,7 +28,6 @@ export interface SystemKeys {
'smtp.host': string,
'smtp.port': number,
'smtp.secure': boolean,
'user': number,
'installid': string,
'server.name': string,
'server.url': string,
@@ -276,11 +275,6 @@ export interface TestCase {
message: string,
}
export interface ContestInfo {
type: 30 | 60,
tid: ObjectID,
}
export interface RecordDoc {
_id: ObjectID;
domainId: string;
@@ -301,11 +295,10 @@ export interface RecordDoc {
judgeAt: Date;
status: number;
progress?: number;
/** pretest/script */
hidden?: boolean;
/** pretest */
input?: string;
contest?: ContestInfo;
/** 0 if pretest&script */
contest?: ObjectID;
}
export interface ScoreboardNode {
@@ -329,7 +322,7 @@ export interface TrainingNode {
export type ExternalProblemId = string; // ${string}:${number}
export type ProblemId = ExternalProblemId | number;
export interface Tdoc<docType = document['TYPE_CONTEST'] | document['TYPE_HOMEWORK'] | document['TYPE_TRAINING']> extends Document {
export interface Tdoc<docType = document['TYPE_CONTEST'] | document['TYPE_TRAINING']> extends Document {
docId: ObjectID,
docType: docType & number,
beginAt: Date,
@@ -461,15 +454,15 @@ export interface ContestRule {
TEXT: string;
check: (args: any) => any;
statusSort: any;
showScoreboard: (tdoc: Tdoc<30 | 60>, now: Date) => boolean;
showSelfRecord: (tdoc: Tdoc<30 | 60>, now: Date) => boolean;
showRecord: (tdoc: Tdoc<30 | 60>, now: Date) => boolean;
stat: (tdoc: Tdoc<30 | 60>, journal: any[]) => ContestStat;
showScoreboard: (tdoc: Tdoc<30>, now: Date) => boolean;
showSelfRecord: (tdoc: Tdoc<30>, now: Date) => boolean;
showRecord: (tdoc: Tdoc<30>, now: Date) => boolean;
stat: (tdoc: Tdoc<30>, journal: any[]) => ContestStat;
scoreboard: (
isExport: boolean, _: (s: string) => string,
tdoc: Tdoc<30 | 60>, pdict: ProblemDict, cursor: Cursor<any>, page: number,
tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor<any>, page: number,
) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>;
ranked: (tdoc: Tdoc<30 | 60>, cursor: Cursor<any>) => Promise<any[]>;
ranked: (tdoc: Tdoc<30>, cursor: Cursor<any>) => Promise<any[]>;
}
export type ContestRules = Dictionary<ContestRule>;
+1 -1
View File
@@ -58,5 +58,5 @@ export async function load() {
export async function loadCli() {
await require('./entry/cli').load();
process.kill(process.pid, 'SIGINT');
setTimeout(() => process.exit(0), 300);
}
+46 -72
View File
@@ -16,8 +16,6 @@ import * as document from './document';
import problem from './problem';
import user from './user';
type Type = 30 | 60;
interface AcmJournal {
rid: ObjectID;
pid: ProblemId;
@@ -439,7 +437,7 @@ function _getStatusJournal(tsdoc) {
export async function add(
domainId: string, title: string, content: string, owner: number,
rule: string, beginAt = new Date(), endAt = new Date(), pids: ProblemId[] = [],
rated = false, data: Partial<Tdoc> = {}, type: Type = document.TYPE_CONTEST,
rated = false, data: Partial<Tdoc> = {},
) {
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
@@ -448,101 +446,79 @@ export async function add(
});
this.RULES[rule].check(data);
await bus.serial('contest/before-add', data);
const res = await document.add(domainId, content, owner, type, null, null, null, {
const res = await document.add(domainId, content, owner, document.TYPE_CONTEST, null, null, null, {
...data, title, rule, beginAt, endAt, pids, attend: 0, rated,
});
await bus.serial('contest/add', data, res);
return res;
}
export async function edit(
domainId: string, tid: ObjectID,
$set: any, type: Type = document.TYPE_CONTEST,
) {
if ($set.rule) {
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
}
const tdoc = await document.get(domainId, type, tid);
export async function edit(domainId: string, tid: ObjectID, $set: any) {
if ($set.rule && !this.RULES[$set.rule]) throw new ValidationError('rule');
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) throw new ContestNotFoundError(domainId, tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
return await document.set(domainId, type, tid, $set);
return await document.set(domainId, document.TYPE_CONTEST, tid, $set);
}
export async function del(domainId: string, tid: ObjectID, type: Type = document.TYPE_CONTEST) {
export async function del(domainId: string, tid: ObjectID) {
await Promise.all([
document.deleteOne(domainId, type, tid),
document.deleteMultiStatus(domainId, type, { docId: tid }),
document.deleteMulti(domainId, document.TYPE_DISCUSSION, { parentType: type, parentId: tid }),
document.deleteOne(domainId, document.TYPE_CONTEST, tid),
document.deleteMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }),
document.deleteMulti(domainId, document.TYPE_DISCUSSION, { parentType: document.TYPE_CONTEST, parentId: tid }),
]);
}
export async function get(domainId: string, tid: ObjectID, type: -1): Promise<Tdoc<30 | 60>>;
export async function get<T extends Type>(domainId: string, tid: ObjectID, type: T): Promise<Tdoc<T>>;
export async function get(domainId: string, tid: ObjectID): Promise<Tdoc<30>>;
export async function get(domainId: string, tid: ObjectID, type: Type | -1 = document.TYPE_CONTEST) {
let tdoc: Tdoc;
if (type === -1) {
tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid);
} else tdoc = await document.get(domainId, type, tid);
export async function get(domainId: string, tid: ObjectID): Promise<Tdoc<30>> {
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) throw new ContestNotFoundError(tid);
return tdoc;
}
export async function getRelated(domainId: string, pid: ProblemId, type: Type = document.TYPE_CONTEST) {
return await document.getMulti(domainId, type, { pids: pid }).toArray();
export async function getRelated(domainId: string, pid: ProblemId) {
return await document.getMulti(domainId, document.TYPE_CONTEST, { pids: pid }).toArray();
}
export function getStatus(
domainId: string, tid: ObjectID, uid: number,
type: 30 | 60 | -1 = document.TYPE_CONTEST,
) {
if (type === -1) type = { $in: [30, 60] } as any;
return document.getStatus(domainId, type, tid, uid);
export function getStatus(domainId: string, tid: ObjectID, uid: number) {
return document.getStatus(domainId, document.TYPE_CONTEST, tid, uid);
}
export async function updateStatus(
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: ProblemId,
status = STATUS.STATUS_WRONG_ANSWER, score = 0, type: 30 | 60 = document.TYPE_CONTEST,
status = STATUS.STATUS_WRONG_ANSWER, score = 0,
) {
const [tdoc, otsdoc] = await Promise.all([
get(domainId, tid, type),
getStatus(domainId, tid, uid, type),
get(domainId, tid),
getStatus(domainId, tid, uid),
]);
if (!otsdoc.attend) throw new ContestNotAttendedError(tid, uid);
const tsdoc = await document.revPushStatus(domainId, type, tid, uid, 'journal', {
const tsdoc = await document.revPushStatus(domainId, document.TYPE_CONTEST, tid, uid, 'journal', {
rid, pid, status, score,
}, 'rid');
const journal = _getStatusJournal(tsdoc);
const stats = RULES[tdoc.rule].stat(tdoc, journal);
return await document.revSetStatus(domainId, type, tid, uid, tsdoc.rev, { journal, ...stats });
return await document.revSetStatus(domainId, document.TYPE_CONTEST, tid, uid, tsdoc.rev, { journal, ...stats });
}
export async function getListStatus(
domainId: string, uid: number, tids: ObjectID[],
type: 30 | 60 = document.TYPE_CONTEST,
) {
export async function getListStatus(domainId: string, uid: number, tids: ObjectID[]) {
const r = {};
// eslint-disable-next-line no-await-in-loop
for (const tid of tids) r[tid.toHexString()] = await getStatus(domainId, tid, uid, type);
for (const tid of tids) r[tid.toHexString()] = await getStatus(domainId, tid, uid);
return r;
}
export async function attend(
domainId: string, tid: ObjectID, uid: number,
type: Type = document.TYPE_CONTEST,
) {
export async function attend(domainId: string, tid: ObjectID, uid: number) {
try {
await document.cappedIncStatus(domainId, type, tid, uid, 'attend', 1, 0, 1);
await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1);
} catch (e) {
throw new ContestAlreadyAttendedError(tid, uid);
}
await document.inc(domainId, type, tid, 'attend', 1);
await document.inc(domainId, document.TYPE_CONTEST, tid, 'attend', 1);
return {};
}
export function getMultiStatus(domainId: string, query: any, docType: 30 | 60 = document.TYPE_CONTEST) {
return document.getMultiStatus(domainId, docType, query);
export function getMultiStatus(domainId: string, query: any) {
return document.getMultiStatus(domainId, document.TYPE_CONTEST, query);
}
export function isNew(tdoc: Tdoc, days = 1) {
@@ -579,30 +555,28 @@ export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: an
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
}
export function count(domainId: string, query: any, type: Type = document.TYPE_CONTEST) {
return document.count(domainId, type, query);
export function count(domainId: string, query: any) {
return document.count(domainId, document.TYPE_CONTEST, query);
}
export function getMulti<K extends Type & keyof document.DocType>(
domainId: string, query: FilterQuery<document.DocType[K]> = {}, type?: K,
export function getMulti(
domainId: string, query: FilterQuery<document.DocType['30']> = {},
) {
return document.getMulti(domainId, type || document.TYPE_CONTEST, query).sort({ beginAt: -1 });
return document.getMulti(domainId, document.TYPE_CONTEST, query).sort({ beginAt: -1 });
}
export async function getAndListStatus(
domainId: string, tid: ObjectID, docType: Type | -1 = document.TYPE_CONTEST,
): Promise<[Tdoc, any[]]> {
export async function getAndListStatus(domainId: string, tid: ObjectID): Promise<[Tdoc, any[]]> {
// TODO(iceboy): projection, pagination.
const tdoc = await get(domainId, tid, docType as any);
const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid })
const tdoc = await get(domainId, tid);
const tsdocs = await document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid })
.sort(RULES[tdoc.rule].statusSort).toArray();
return [tdoc, tsdocs];
}
export async function recalcStatus(domainId: string, tid: ObjectID, type: Type = document.TYPE_CONTEST) {
export async function recalcStatus(domainId: string, tid: ObjectID) {
const [tdoc, tsdocs] = await Promise.all([
document.get(domainId, type, tid),
document.getMultiStatus(domainId, type, { docId: tid }).toArray(),
document.get(domainId, document.TYPE_CONTEST, tid),
document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }).toArray(),
]);
const tasks = [];
for (const tsdoc of tsdocs || []) {
@@ -611,7 +585,7 @@ export async function recalcStatus(domainId: string, tid: ObjectID, type: Type =
const stats = RULES[tdoc.rule].stat(tdoc, journal);
tasks.push(
document.revSetStatus(
domainId, type, tid,
domainId, document.TYPE_CONTEST, tid,
tsdoc.uid, tsdoc.rev, { journal, ...stats },
),
);
@@ -624,19 +598,19 @@ export function canViewHiddenScoreboard() {
return this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
}
export function canShowRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
export function canShowRecord(tdoc: Tdoc<30>, allowPermOverride = true) {
if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
return false;
}
export function canShowSelfRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
export function canShowSelfRecord(tdoc: Tdoc<30>, allowPermOverride = true) {
if (RULES[tdoc.rule].showSelfRecord(tdoc, new Date())) return true;
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
return false;
}
export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
export function canShowScoreboard(tdoc: Tdoc<30>, allowPermOverride = true) {
if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
return false;
@@ -644,11 +618,11 @@ 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,
isExport = false, page: number,
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict, number]> {
const tdoc = await get(domainId, tid, docType);
const tdoc = await get(domainId, tid);
if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
const tsdocsCursor = getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort);
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(
isExport, this.translate.bind(this),
+1 -1
View File
@@ -252,7 +252,7 @@ export async function getVnode(domainId: string, type: number, id: string, uid?:
if ([document.TYPE_CONTEST, document.TYPE_TRAINING, document.TYPE_HOMEWORK].includes(type as any)) {
const model = type === document.TYPE_TRAINING ? training : contest;
const _id = new ObjectID(id);
const tdoc = await model.get(domainId, _id, type as any);
const tdoc = await model.get(domainId, _id);
if (!tdoc) throw new DiscussionNodeNotFoundError(id);
if (uid) {
const tsdoc = await model.getStatus(domainId, _id, uid);
-1
View File
@@ -35,7 +35,6 @@ export interface DocType {
[TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc,
[TYPE_CONTEST]: Tdoc,
[TYPE_TRAINING]: TrainingDoc,
[TYPE_HOMEWORK]: Tdoc,
}
export interface DocStatusType {
+10 -14
View File
@@ -8,8 +8,8 @@ import {
} from 'mongodb';
import { ProblemNotFoundError } from '../error';
import {
ContestInfo, ExternalProblemId, FileInfo,
ProblemConfigFile, RecordDoc,
ExternalProblemId, FileInfo, ProblemConfigFile,
RecordDoc,
} from '../interface';
import * as bus from '../service/bus';
import db from '../service/db';
@@ -23,9 +23,8 @@ class RecordModel {
static coll: Collection<RecordDoc> = db.collection('record');
static PROJECTION_LIST: (keyof RecordDoc)[] = [
'_id', 'score', 'time', 'memory', 'lang',
'uid', 'pid', 'rejudged', 'hidden', 'progress',
'uid', 'pid', 'rejudged', 'progress', 'pdomain',
'contest', 'judger', 'judgeAt', 'status', 'domainId',
'pdomain',
];
static async submissionPriority(uid: number, base: number = 0) {
@@ -98,7 +97,7 @@ class RecordModel {
static async add(
domainId: string, pid: ExternalProblemId | number, uid: number,
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | string,
lang: string, code: string, addTask: boolean, contestOrConfig?: ObjectID | string,
) {
let pdomain = domainId;
if (typeof pid === 'string') {
@@ -117,7 +116,6 @@ class RecordModel {
score: 0,
time: 0,
memory: 0,
hidden: false,
judgeTexts: [],
compilerTexts: [],
testCases: [],
@@ -129,7 +127,7 @@ class RecordModel {
if (typeof contestOrConfig === 'string') {
// is Run
data.input = contestOrConfig;
data.hidden = true;
data.contest = new ObjectID('000000000000000000000000');
} else {
data.contest = contestOrConfig;
isContest = true;
@@ -207,16 +205,14 @@ class RecordModel {
}
static async getList(
domainId: string, rids: ObjectID[], showHidden: boolean, fields?: (keyof RecordDoc)[],
domainId: string, rids: ObjectID[], fields?: (keyof RecordDoc)[],
): Promise<Record<string, Partial<RecordDoc>>> {
const r: Record<string, RecordDoc> = {};
rids = Array.from(new Set(rids));
let cursor = RecordModel.coll.find({ domainId, _id: { $in: rids } });
if (fields) cursor = cursor.project(buildProjection(fields));
const rdocs = await cursor.toArray();
for (const rdoc of rdocs) {
if (!rdoc.hidden || showHidden) r[rdoc._id.toHexString()] = rdoc;
}
for (const rdoc of rdocs) r[rdoc._id.toHexString()] = rdoc;
return r;
}
}
@@ -230,9 +226,9 @@ bus.on('domain/delete', (domainId) => Promise.all([
bus.once('app/started', () => db.ensureIndexes(
RecordModel.coll,
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, _id: -1 }, name: 'basic' },
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, uid: 1, _id: -1 }, name: 'withUser' },
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, pid: 1, _id: -1 }, name: 'withProblem' },
{ key: { domainId: 1, contest: 1, _id: -1 }, name: 'basic' },
{ key: { domainId: 1, contest: 1, uid: 1, _id: -1 }, name: 'withUser' },
{ key: { domainId: 1, contest: 1, pid: 1, _id: -1 }, name: 'withProblem' },
));
export default RecordModel;
+5
View File
@@ -103,6 +103,11 @@ async function cleanFiles() {
}
TaskModel.Worker.addHandler('storage.prune', cleanFiles);
bus.once('app/started', async () => {
if (process.env.NODE_APP_INSTANCE !== '0') return;
await db.ensureIndexes(
StorageModel.coll,
{ key: { path: 1, autoDelete: 1 }, sparse: true, name: 'autoDelete' },
);
if (!await TaskModel.count({ type: 'schedule', subType: 'storage.prune' })) {
await TaskModel.add({
type: 'schedule',
+1
View File
@@ -137,6 +137,7 @@ class TaskModel {
const id = hostname();
Worker.addHandler('task.daily', async () => {
await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectID('000000000000000000000000') });
await global.Hydro.script.rp?.run({}, new Logger('task/rp').debug);
await global.Hydro.script.problemStat?.run({}, new Logger('task/problem').debug);
});
+1 -1
View File
@@ -286,7 +286,7 @@ class UserModel {
@ArgMethod
static async ensureVuser(uname: string) {
const [[min], current] = await Promise.all([
collV.find({}).sort({ _id: -1 }).limit(1).toArray(),
collV.find({}).sort({ _id: 1 }).limit(1).toArray(),
collV.findOne({ unameLower: uname.toLowerCase() }),
]);
if (current) return current._id;
+12 -6
View File
@@ -1,4 +1,6 @@
/* eslint-disable no-constant-condition */
/* eslint-disable no-await-in-loop */
import { ObjectID } from 'mongodb';
import { STATUS } from '../model/builtin';
import * as document from '../model/document';
import db from '../service/db';
@@ -6,11 +8,12 @@ import db from '../service/db';
export const description = 'Recalculates nSubmit and nAccept in problem status.';
const sumStatus = (status) => ({ $sum: { $cond: [{ $eq: ['$status', status] }, 1, 0] } });
const $match = { contest: { $ne: new ObjectID('000000000000000000000000') } };
export async function udoc(report) {
report({ message: 'Udoc' });
const pipeline = [
{ $match: { hidden: false, type: { $ne: 'run' } } },
{ $match },
{
$group: {
_id: { domainId: '$domainId', pid: '$pid', uid: '$uid' },
@@ -28,8 +31,9 @@ export async function udoc(report) {
];
let bulk = db.collection('domain.user').initializeUnorderedBulkOp();
const cursor = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
while (await cursor.hasNext()) {
while (true) {
const adoc = await cursor.next() as any;
if (!adoc) break;
bulk.find({
domainId: adoc._id.domainId,
uid: adoc._id.uid,
@@ -50,7 +54,7 @@ export async function udoc(report) {
export async function psdoc(report) {
report({ message: 'Psdoc' });
const pipeline = [
{ $match: { hidden: false, type: { $ne: 'run' } } },
{ $match },
{
$group: {
_id: { domainId: '$domainId', pid: '$pid', uid: '$uid' },
@@ -59,8 +63,9 @@ export async function psdoc(report) {
},
];
const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
while (await data.hasNext()) {
while (true) {
const adoc = await data.next() as any;
if (!adoc) break;
await document.setStatus(adoc._id.domainId, document.TYPE_PROBLEM, adoc._id.pid, adoc._id.uid, { nSubmit: adoc.nSubmit });
}
}
@@ -68,7 +73,7 @@ export async function psdoc(report) {
export async function pdoc(report) {
report({ message: 'Pdoc' });
const pipeline = [
{ $match: { hidden: false, type: { $ne: 'run' } } },
{ $match },
{
$group: {
_id: { domainId: '$domainId', pid: '$pid', uid: '$uid' },
@@ -106,8 +111,9 @@ export async function pdoc(report) {
let bulk = db.collection('document').initializeUnorderedBulkOp();
const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
let cnt = 0;
while (await data.hasNext()) {
while (true) {
const adoc = await data.next() as any;
if (!adoc) break;
const $set = {
nSubmit: adoc.nSubmit,
nAccept: adoc.nAccept,
+4 -4
View File
@@ -32,7 +32,7 @@ async function runProblem(...arg: any[]) {
const psdocs = await problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
).limit(100).skip((page - 1) * 100).project({ rid: 1, uid: 1 }).toArray();
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid));
for (const psdoc of psdocs) {
if (rdict[psdoc.rid.toHexString()]) {
const rp = rdict[psdoc.rid.toHexString()].score * p;
@@ -48,12 +48,12 @@ async function runContest(
): Promise<void>;
async function runContest(...arg: any[]) {
const start = new Date().getTime();
const tdoc: Tdoc<30 | 60> = (typeof arg[0] === 'string')
? await contest.get(arg[0], arg[1], -1)
const tdoc: Tdoc<30> = (typeof arg[0] === 'string')
? await contest.get(arg[0], arg[1])
: arg[0];
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
const report = (typeof arg[0] === 'string') ? arg[3] : arg[2];
const cursor = contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType)
const cursor = contest.getMultiStatus(tdoc.domainId, tdoc.docId)
.sort(contest.RULES[tdoc.rule].statusSort);
if (!await cursor.count()) return;
const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor);
+1 -9
View File
@@ -1,11 +1,9 @@
import assert from 'assert';
import { Readable } from 'stream';
import { URL } from 'url';
import { createReadStream } from 'fs-extra';
import { BucketItem, Client, ItemBucketMetadata } from 'minio';
import { Logger } from '../logger';
import * as system from '../model/system';
import { streamToBuffer } from '../utils';
const logger = new Logger('storage');
@@ -116,19 +114,13 @@ class StorageService {
user: parseAlternativeEndpointUrl(this.opts.endPointForUser),
judge: parseAlternativeEndpointUrl(this.opts.endPointForJudge),
};
await this.put('storage.test', Buffer.from('test'));
const result = await streamToBuffer(await this.get('storage.test'));
assert(result.toString() === 'test');
await this.del('storage.test');
logger.success('Storage connected.');
this.error = null;
} catch (e) {
logger.warn('Storage init fail. will retry later.');
if (process.env.DEV) logger.warn(e);
this.error = e.toString();
setTimeout(async () => {
await this.start();
}, 10000);
setTimeout(() => this.start(), 10000);
}
}
+14 -3
View File
@@ -39,7 +39,7 @@ const scripts: UpgradeScript[] = [
// Init
async function _1_2() {
const ddoc = await domain.get('system');
if (!ddoc) await domain.add('system', 1, 'Hydro', 'Hydro System');
if (!ddoc) await domain.add('system', 1, 'Hydro', 'Welcome to Hydro!');
// TODO discussion node?
return true;
},
@@ -451,7 +451,6 @@ const scripts: UpgradeScript[] = [
const _FRESH_INSTALL_IGNORE = 1;
// Ignore drop index failure
await db.collection('storage').dropIndex('path_1').catch(() => { });
await db.collection('storage').createIndex({ path: 1, autoDelete: 1 }, { sparse: true });
await db.collection('storage').updateMany(
{ autoDelete: { $gte: moment().add(5, 'days').toDate() } },
{ $unset: { autoDelete: '' } },
@@ -524,7 +523,7 @@ const scripts: UpgradeScript[] = [
await iterateAllDomain(async (ddoc) => {
const ddocs = await discussion.getMulti(ddoc._id, {
docType: document.TYPE_DISCUSSION,
parentType: { $in: [document.TYPE_CONTEST, document.TYPE_HOMEWORK, document.TYPE_TRAINING] },
parentType: { $in: [document.TYPE_CONTEST, 60, document.TYPE_TRAINING] },
parentId: { $type: 'string' },
}).toArray();
for (const doc of ddocs) {
@@ -535,6 +534,18 @@ const scripts: UpgradeScript[] = [
});
return true;
},
null,
async function _46_47() {
const _FRESH_INSTALL_IGNORE = 1;
await document.coll.updateMany({ docType: document.TYPE_HOMEWORK }, { $set: { docType: document.TYPE_CONTEST } });
await document.collStatus.updateMany({ docType: document.TYPE_HOMEWORK }, { $set: { docType: document.TYPE_CONTEST } });
await RecordModel.coll.deleteMany({ 'contest.tid': { $ne: null }, hidden: true });
await RecordModel.coll.updateMany({}, { $unset: { hidden: '' } });
await RecordModel.coll.updateMany({ 'contest.tid': { $exists: true } }, { $rename: { 'contest.tid': 'contest1' } });
await RecordModel.coll.updateMany({ contest1: { $exists: true } }, { $rename: { contest1: 'contest' } });
await RecordModel.coll.updateMany({ contest: { $exists: true } }, { $unset: { contest: '' } });
return true;
},
];
export default scripts;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/migrate-vijos",
"version": "1.5.0",
"version": "1.5.1",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <i@undefined.moe>",
+3 -8
View File
@@ -5,7 +5,7 @@ import {
} from 'hydrooj';
const dst = global.Hydro.service.db;
const { discussion, document } = global.Hydro.model;
const { discussion } = global.Hydro.model;
const map = {};
const pid = (id) => {
@@ -201,6 +201,7 @@ const tasks = {
userfile_usage: null,
},
record: async (doc) => {
if (doc.hidden) return null;
const testCases: TestCase[] = [];
for (const c of doc.cases || []) {
testCases.push({
@@ -227,16 +228,10 @@ const tasks = {
judgeTexts: doc.judge_texts || [],
compilerTexts: doc.compiler_texts || [],
testCases,
hidden: !!doc.hidden,
rejudged: !!doc.rejudged,
};
if (doc.rejudged) rdoc.rejudged = true;
if (doc.tid) {
rdoc.contest = {
type: doc.ttype || document.TYPE_CONTEST,
tid: doc.tid,
};
}
if (doc.tid) rdoc.contest = doc.tid;
return rdoc;
},
domain: {