diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index a709d58c8..382e15995 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.32.4", + "version": "2.32.5", "bin": "bin/hydrooj.js", "main": "src/loader", "module": "src/loader", diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 8da22862d..ff3012d7a 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -307,7 +307,7 @@ class HomeDomainHandler extends Handler { const canManage = {}; for (const ddoc of ddocs) { // eslint-disable-next-line no-await-in-loop - const udoc = await user.getById(ddoc._id, this.user._id); + const udoc = (await user.getById(ddoc._id, this.user._id))!; canManage[ddoc._id] = udoc.hasPerm(PERM.PERM_EDIT_DOMAIN) || udoc.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN); } @@ -415,7 +415,7 @@ class HomeMessagesConnectionHandler extends ConnectionHandler { async onMessageReceived(uid: number, mdoc: MessageDoc) { if (uid !== this.user._id) return; - const udoc = await user.getById(this.domainId, mdoc.from); + const udoc = (await user.getById(this.domainId, mdoc.from))!; udoc.avatarUrl = avatar(udoc.avatar, 64); this.send({ udoc, mdoc }); } diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index cb39ab6ba..96c102de9 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -1,4 +1,5 @@ import { FilterQuery, ObjectID } from 'mongodb'; +import { pick } from 'lodash'; import { postJudge } from './judge'; import { ContestNotFoundError, PermissionError, ProblemNotFoundError, @@ -32,6 +33,7 @@ class RecordListHandler extends Handler { uidOrName?: string, status?: number, full = false, all = false, ) { let tdoc = null; + let invalid = false; this.response.template = 'record_main.html'; const q: FilterQuery = { 'contest.tid': tid, hidden: false }; if (full) uidOrName = this.user._id.toString(); @@ -41,23 +43,19 @@ class RecordListHandler extends Handler { if (!contest.canShowScoreboard.call(this, tdoc, true)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); } if (uidOrName) { - let udoc = await user.getById(domainId, +uidOrName); + const udoc = await user.getById(domainId, +uidOrName) ?? await user.getByUname(domainId, uidOrName); if (udoc) q.uid = udoc._id; - else { - udoc = await user.getByUname(domainId, uidOrName); - if (udoc) q.uid = udoc._id; - else q.uid = null; - } + else invalid = true; } if (pid) { const pdoc = await problem.get(domainId, pid); if (pdoc) q.pid = pdoc.docId; - else q.pid = null; + else invalid = true; } if (status) q.status = status; let cursor = record.getMulti(domainId, q).sort('_id', -1); if (!full) cursor = cursor.project(buildProjection(record.PROJECTION_LIST)); - const [rdocs] = await paginate(cursor, page, full ? 10 : system.get('pagination.record')); + const [rdocs] = invalid ? [[]] : await paginate(cursor, page, full ? 10 : system.get('pagination.record')); const canViewProblem = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM); const canViewProblemHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); const [udict, pdict] = full ? [{}, {}] @@ -104,7 +102,7 @@ class RecordDetailHandler extends Handler { } if (rdoc.uid !== this.user._id && !this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) { if (!this.user.hasPerm(PERM.PERM_READ_RECORD_CODE)) { - rdoc.code = null; + rdoc.code = ''; rdoc.compilerTexts = []; } } @@ -118,7 +116,8 @@ class RecordDetailHandler extends Handler { || !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM) || (!rdoc.contest && pdoc.hidden && !this.user.own(pdoc) && !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) ) { - throw new PermissionError(PERM.PERM_READ_RECORD_CODE); + if (this.user._id !== rdoc.uid) throw new PermissionError(PERM.PERM_READ_RECORD_CODE); + else pdoc = { ...problem.default, ...pdoc ? pick(pdoc, ['domainId', 'docId', 'pid']) : {} }; } this.response.body = { udoc, rdoc, pdoc }; if (rdoc.contest) { @@ -154,14 +153,14 @@ class RecordDetailHandler extends Handler { record.update(domainId, rid, $set), TaskModel.deleteMany({ rid: rdoc._id }), ]); - await postJudge(latest); + if (latest) await postJudge(latest); } this.back(); } } class RecordMainConnectionHandler extends ConnectionHandler { - cleanup: bus.Disposable; + cleanup: bus.Disposable = () => { }; tid: string; uid: number; pdomain: string; @@ -218,7 +217,7 @@ class RecordMainConnectionHandler extends ConnectionHandler { if (rdoc.domainId !== this.domainId) return; if (rdoc.contest && rdoc.contest.tid.toString() !== this.tid) return; if (this.uid && rdoc.uid !== this.uid) return; - if (this.pid && (rdoc.pid !== this.pid || rdoc.pdomain !== this.pdomain)) return; + // eslint-disable-next-line prefer-const let [udoc, pdoc] = await Promise.all([ user.getById(this.domainId, rdoc.uid), @@ -241,12 +240,13 @@ class RecordMainConnectionHandler extends ConnectionHandler { } class RecordDetailConnectionHandler extends ConnectionHandler { - cleanup: bus.Disposable; - rid: string; + cleanup: bus.Disposable = () => { }; + rid: string = ''; @param('rid', Types.ObjectID) async prepare(domainId: string, rid: ObjectID) { 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); let canView = this.user.own(tdoc); @@ -256,13 +256,13 @@ class RecordDetailConnectionHandler extends ConnectionHandler { } if (rdoc.uid !== this.user._id && !this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) { if (!this.user.hasPerm(PERM.PERM_READ_RECORD_CODE)) { - rdoc.code = null; + rdoc.code = ''; rdoc.compilerTexts = []; } } const pdoc = await problem.get(rdoc.pdomain, rdoc.pid); - let canView = this.user.own(pdoc); - canView ||= !pdoc.hidden && this.user.hasPerm(PERM.PERM_VIEW_PROBLEM); + let canView = pdoc && this.user.own(pdoc); + canView ||= !pdoc?.hidden && this.user.hasPerm(PERM.PERM_VIEW_PROBLEM); canView ||= this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); canView ||= !!rdoc.contest || this.user._id === rdoc.uid; if (!canView) throw new PermissionError(PERM.PERM_READ_RECORD_CODE); diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index b9046f2cf..8671c548e 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -10,7 +10,7 @@ import domain from './domain'; import storage from './storage'; import { buildProjection } from '../utils'; import type { - ProblemStatusDoc, ProblemDict, Document, ProblemId, + ProblemStatusDoc, ProblemDict, Document, ProblemId, DomainDoc, } from '../interface'; import { ArrayKeys, MaybeArray, NumberKeys, Projection, @@ -77,7 +77,7 @@ export class ProblemModel { }; static async add( - domainId: string, pid: string = null, title: string, content: string, owner: number, + domainId: string, pid: string = '', title: string, content: string, owner: number, tag: string[] = [], hidden = false, ) { const [doc] = await ProblemModel.getMulti(domainId, {}) @@ -88,7 +88,7 @@ export class ProblemModel { } static async addWithId( - domainId: string, docId: number, pid: string = null, title: string, + domainId: string, docId: number, pid: string = '', title: string, content: string, owner: number, tag: string[] = [], hidden = false, ) { const args: Partial = { @@ -107,7 +107,7 @@ export class ProblemModel { static async get( domainId: string, pid: string | number, projection: Projection = ProblemModel.PROJECTION_PUBLIC, - ): Promise { + ): Promise { if (typeof pid !== 'number') { if (Number.isSafeInteger(parseInt(pid, 10))) pid = parseInt(pid, 10); } @@ -192,6 +192,7 @@ export class ProblemModel { const meta = await storage.getMeta(`problem/${domainId}/${pid}/testdata/${name}`); if (!meta) throw new Error('Upload failed'); const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) }; + payload.lastModified ||= new Date(); if (!fileinfo) await ProblemModel.push(domainId, pid, 'data', { _id: name, ...payload }); else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'data', name, payload); await bus.emit('problem/addTestdata', domainId, pid, name, payload); @@ -243,20 +244,20 @@ export class ProblemModel { })), 'domainId', ); - const r = {}; - const l = {}; + const r: Record = {}; + const l: Record = {}; const ddocs = await Promise.all(Object.keys(parsed).map((i) => domain.get(i))); const f = ddocs.filter((i) => !( - i?._id === domainId - || i?.share === '*' - || (`,${(i?.share || '').replace(/,/g, ',').split(',').map((q) => q.trim()).join(',')},`).includes(`,${domainId},`) - )); + !i || i._id === domainId + || i.share === '*' + || (`,${(i.share || '').replace(/,/g, ',').split(',').map((q) => q.trim()).join(',')},`).includes(`,${domainId},`) + )) as DomainDoc[]; if (f.length) { if (doThrow) throw new ProblemNotFoundError(f[0]._id, parsed[f[0]._id][0].pid); else { for (const sf of f) { for (const pinfo of parsed[sf._id]) { - r[pinfo.pid] = { ...ProblemModel.default, domainId: sf._id, pid: pinfo.pid }; + r[pinfo.pid] = { ...ProblemModel.default, domainId: sf._id, pid: pinfo.pid.toString() }; } delete parsed[sf._id]; } @@ -290,7 +291,7 @@ export class ProblemModel { for (const pid of pids) { if (!(r[pid] || l[pid])) { if (doThrow) throw new ProblemNotFoundError(domainId, pid); - else r[pid] = { ...ProblemModel.default, domainId, pid }; + else r[pid] = { ...ProblemModel.default, domainId, pid: pid.toString() }; } } }