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