From 4daf8bac8053889d239697a2746f6fd0bf0232e5 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 23 Sep 2021 18:15:17 +0800 Subject: [PATCH] core: database structure update --- packages/hydrooj/package.json | 4 +- packages/hydrooj/src/handler/contest.ts | 25 ++--- packages/hydrooj/src/handler/discussion.ts | 2 +- packages/hydrooj/src/handler/domain.ts | 4 +- packages/hydrooj/src/handler/home.ts | 8 +- packages/hydrooj/src/handler/homework.ts | 110 +++++++------------ packages/hydrooj/src/handler/judge.ts | 4 +- packages/hydrooj/src/handler/problem.ts | 9 +- packages/hydrooj/src/handler/record.ts | 33 +++--- packages/hydrooj/src/interface.ts | 25 ++--- packages/hydrooj/src/loader.ts | 2 +- packages/hydrooj/src/model/contest.ts | 118 ++++++++------------- packages/hydrooj/src/model/discussion.ts | 2 +- packages/hydrooj/src/model/document.ts | 1 - packages/hydrooj/src/model/record.ts | 24 ++--- packages/hydrooj/src/model/storage.ts | 5 + packages/hydrooj/src/model/task.ts | 1 + packages/hydrooj/src/model/user.ts | 2 +- packages/hydrooj/src/script/problemStat.ts | 18 ++-- packages/hydrooj/src/script/rating.ts | 8 +- packages/hydrooj/src/service/storage.ts | 10 +- packages/hydrooj/src/upgrade.ts | 17 ++- packages/migrate-vijos/package.json | 2 +- packages/migrate-vijos/script.ts | 11 +- 24 files changed, 182 insertions(+), 263 deletions(-) diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 3bdc60c5a..3ab4e2552 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -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", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index f8b741c52..70cf29681 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -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) { diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index 65982047f..609902f9e 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -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 { diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 54a980d90..2a0e6724c 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -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'; diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 80bcc9ffc..14d344594 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -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), diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index ff079a15f..08200cbf3 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -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); } diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 0a18576ab..c20e55deb 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -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) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 1511da2b6..478e6ff41 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -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'); } diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 5707a05a6..95b596a49 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -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 = { 'contest.tid': tid, hidden: false }; + const q: FilterQuery = { 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); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index a4d3dae1c..1ebb50569 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -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 extends Document { +export interface Tdoc 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, page: number, + tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor, page: number, ) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>; - ranked: (tdoc: Tdoc<30 | 60>, cursor: Cursor) => Promise; + ranked: (tdoc: Tdoc<30>, cursor: Cursor) => Promise; } export type ContestRules = Dictionary; diff --git a/packages/hydrooj/src/loader.ts b/packages/hydrooj/src/loader.ts index 4e3dae106..1a7bb444e 100644 --- a/packages/hydrooj/src/loader.ts +++ b/packages/hydrooj/src/loader.ts @@ -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); } diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 6b7192f10..f2438a145 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -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 = {}, type: Type = document.TYPE_CONTEST, + rated = false, data: Partial = {}, ) { 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>; -export async function get(domainId: string, tid: ObjectID, type: T): Promise>; -export async function get(domainId: string, tid: ObjectID): Promise>; -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> { + 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( - domainId: string, query: FilterQuery = {}, type?: K, +export function getMulti( + domainId: string, query: FilterQuery = {}, ) { - 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), diff --git a/packages/hydrooj/src/model/discussion.ts b/packages/hydrooj/src/model/discussion.ts index 47c7efec4..23e7d909d 100644 --- a/packages/hydrooj/src/model/discussion.ts +++ b/packages/hydrooj/src/model/discussion.ts @@ -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); diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 6a1f8b114..4f90ef4f7 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -35,7 +35,6 @@ export interface DocType { [TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc, [TYPE_CONTEST]: Tdoc, [TYPE_TRAINING]: TrainingDoc, - [TYPE_HOMEWORK]: Tdoc, } export interface DocStatusType { diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 8973cc3a5..df2969c9d 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -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 = 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>> { const r: Record = {}; 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; diff --git a/packages/hydrooj/src/model/storage.ts b/packages/hydrooj/src/model/storage.ts index 55a17d7d3..a589c70f3 100644 --- a/packages/hydrooj/src/model/storage.ts +++ b/packages/hydrooj/src/model/storage.ts @@ -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', diff --git a/packages/hydrooj/src/model/task.ts b/packages/hydrooj/src/model/task.ts index 737a416b0..58c2856ec 100644 --- a/packages/hydrooj/src/model/task.ts +++ b/packages/hydrooj/src/model/task.ts @@ -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); }); diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 91b5ffdfe..8624c3c7e 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -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; diff --git a/packages/hydrooj/src/script/problemStat.ts b/packages/hydrooj/src/script/problemStat.ts index 4128d4154..d6dc85c35 100644 --- a/packages/hydrooj/src/script/problemStat.ts +++ b/packages/hydrooj/src/script/problemStat.ts @@ -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, diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index 03ce579b0..5bca1d440 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -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; 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); diff --git a/packages/hydrooj/src/service/storage.ts b/packages/hydrooj/src/service/storage.ts index d6a7c9ccf..45065684a 100644 --- a/packages/hydrooj/src/service/storage.ts +++ b/packages/hydrooj/src/service/storage.ts @@ -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); } } diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 7d78f8629..7892aff49 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -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; diff --git a/packages/migrate-vijos/package.json b/packages/migrate-vijos/package.json index 53d178284..334eda1e1 100644 --- a/packages/migrate-vijos/package.json +++ b/packages/migrate-vijos/package.json @@ -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 ", diff --git a/packages/migrate-vijos/script.ts b/packages/migrate-vijos/script.ts index 981d8fe53..2c8e57396 100644 --- a/packages/migrate-vijos/script.ts +++ b/packages/migrate-vijos/script.ts @@ -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: {