From 42b73f36a4a71f359c7ce03e1c19e8cfe6af3441 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 25 Oct 2021 15:26:14 +0800 Subject: [PATCH] core: update contest path --- packages/hydrojudge/locales/zh.yaml | 1 + packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/hosts/hydro.ts | 6 +- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/contest.ts | 182 +----------------- packages/hydrooj/src/handler/homework.ts | 174 +---------------- packages/hydrooj/src/handler/problem.ts | 98 ++++++---- packages/hydrooj/src/handler/record.ts | 1 + packages/hydrooj/src/model/contest.ts | 6 +- packages/ui-default/package.json | 2 +- .../templates/components/problem.html | 5 +- .../ui-default/templates/contest_detail.html | 4 +- .../templates/contest_scoreboard.html | 2 +- .../ui-default/templates/homework_detail.html | 6 +- .../templates/partials/contest_sidebar.html | 6 +- .../templates/partials/homework_sidebar.html | 6 +- .../templates/partials/problem_sidebar.html | 11 +- .../partials/problem_sidebar_contest.html | 12 +- .../partials/problem_sidebar_homework.html | 14 +- .../partials/problem_sidebar_normal.html | 15 +- .../ui-default/templates/problem_detail.html | 4 +- .../ui-default/templates/record_detail.html | 20 +- 22 files changed, 131 insertions(+), 448 deletions(-) diff --git a/packages/hydrojudge/locales/zh.yaml b/packages/hydrojudge/locales/zh.yaml index a2c9175e2..da4c30d3d 100644 --- a/packages/hydrojudge/locales/zh.yaml +++ b/packages/hydrojudge/locales/zh.yaml @@ -23,6 +23,7 @@ Sandbox Host: 沙箱地址 Standard answer longer than user output.: 标准输出比选手输出长。 Stdout/Stderr size: 标准输出/错误流输出大小限制 Strict Memory Mode: 严格内存限制 +Syncing testdata, please wait...: 正在同步测试数据,请稍候... Temp directory: 临时目录 Testdata cache directory: 测试数据缓存目录 Testdata configuration incorrect.: 题目配置错误。 diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 428a6c102..cc53c5283 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.12.1", + "version": "2.12.2", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index 1fa6c3b53..475009a42 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -65,8 +65,8 @@ class JudgeTask { this.clean = []; await Lock.aquire(`${this.host}/${this.source}/${this.rid}`); fs.ensureDirSync(this.tmpdir); - tmpfs.mount(this.tmpdir, '512m'); - log.info('Submission: %s/%s/%s pid=%s', this.host, this.source, this.rid); + tmpfs.mount(this.tmpdir, getConfig('tmpfs_size')); + log.info('Submission: %s/%s/%s', this.host, this.source, this.rid); try { if (typeof this.input === 'string') await this.run(); else await this.doSubmission(); @@ -204,7 +204,7 @@ export default class Hydro { } if (filenames.length) { log.info(`Getting problem data: ${this.config.host}/${source}`); - if (next) next({ judge_text: '正在同步测试数据,请稍候' }); + if (next) next({ judge_text: 'Syncing testdata, please wait...' }); await this.ensureLogin(); const res = await this.axios.post(`/d/${domainId}/judge/files`, { pid: +pid, diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 09766f3a3..ac58466f8 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "3.0.0", + "version": "3.1.0", "bin": "bin/hydrooj.js", "main": "src/loader", "module": "src/loader", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 66001efb4..9d57e7b67 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -1,28 +1,20 @@ import AdmZip from 'adm-zip'; -import { intersection } from 'lodash'; -import { lookup } from 'mime-types'; import moment from 'moment-timezone'; import { ObjectID } from 'mongodb'; import { Time } from '@hydrooj/utils/lib/utils'; import { - BadRequestError, ContestNotAttendedError, ContestNotFoundError, - ContestNotLiveError, InvalidTokenError, PermissionError, - ProblemNotFoundError, RecordNotFoundError, ValidationError, + ContestNotFoundError, ContestNotLiveError, InvalidTokenError, + PermissionError, ValidationError, } from '../error'; -import { - ProblemDoc, Tdoc, User, -} from '../interface'; +import { Tdoc } from '../interface'; import paginate from '../lib/paginate'; import { PERM, PRIV } from '../model/builtin'; import * as contest from '../model/contest'; -import domain from '../model/domain'; import message from '../model/message'; import problem from '../model/problem'; import record from '../model/record'; -import storage from '../model/storage'; import * as system from '../model/system'; import user from '../model/user'; -import * as bus from '../service/bus'; import { Handler, param, Route, Types, } from '../service/server'; @@ -56,9 +48,7 @@ export class ContestDetailHandler extends Handler { ]); const psdict = {}; let rdict = {}; - let attended: boolean; if (tsdoc) { - attended = tsdoc.attend === 1; for (const pdetail of tsdoc.journal || []) psdict[pdetail.pid] = pdetail; if (contest.canShowSelfRecord.call(this, tdoc)) { const q = []; @@ -67,16 +57,10 @@ export class ContestDetailHandler extends Handler { } 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 index = tdoc.pids.map((i) => i.toString()); - for (const key in pdict) { - pdict[key].tag.length = 0; - const i = (index.indexOf(key) + 10).toString(36).toUpperCase(); - if (i !== '9') pdict[key].pid = i; } + const udict = await user.getList(domainId, [tdoc.owner]); this.response.body = { - tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page, + tdoc, tsdoc, udict, pdict, psdict, rdict, page, }; } @@ -118,7 +102,7 @@ export class ContestBroadcastHandler extends Handler { @param('content', Types.Content) async post(domainId: string, tid: ObjectID, content: string) { const tdoc = await contest.get(domainId, tid); - if (!this.user.own(tdoc)) throw new PermissionError('Boardcast Message'); + if (!this.user.own(tdoc)) throw new PermissionError('Broadcast Message'); const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray(); const uids: number[] = Array.from(new Set(tsdocs.map((tsdoc) => tsdoc.uid))); await Promise.all( @@ -241,157 +225,6 @@ export class ContestEditHandler extends Handler { } } -export class ContestProblemHandler extends Handler { - tdoc: Tdoc<30>; - pdoc: ProblemDoc; - tsdoc: any; - udoc: User; - attended: boolean; - - @param('tid', Types.ObjectID) - @param('pid', Types.Name) - async _prepare(domainId: string, tid: ObjectID, _pid: string) { - this.tdoc = await contest.get(domainId, tid); - const pid = this.tdoc.pids[parseInt(_pid, 36) - 10]; - if (!pid) throw new ProblemNotFoundError(domainId, tid, _pid); - [this.pdoc, this.tsdoc, this.udoc] = await Promise.all([ - problem.get(domainId, pid), - contest.getStatus(domainId, tid, this.user._id), - user.getById(domainId, this.tdoc.owner), - ]); - if (!this.pdoc) throw new ProblemNotFoundError(domainId, pid); - this.pdoc.pid = _pid; - this.pdoc.tag.length = 0; - this.attended = this.tsdoc && this.tsdoc.attend === 1; - if (this.pdoc.reference) { - const pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); - this.pdoc.config = pdoc.config; - } - const showAccept = contest.canShowScoreboard.call(this, this.tdoc, true); - if (!showAccept) this.pdoc.nAccept = 0; - if (contest.isNotStarted(this.tdoc)) throw new ContestNotLiveError(tid); - if (!contest.isDone(this.tdoc) && !this.attended) throw new ContestNotAttendedError(tid); - this.response.template = 'problem_detail.html'; - this.response.body = { - showAccept, - tdoc: this.tdoc, - tsdoc: this.tsdoc, - pdoc: this.pdoc, - udoc: this.udoc, - attended: this.attended, - page_name: 'contest_detail_problem', - }; - } - - @param('tid', Types.ObjectID) - @param('pid', Types.Name) - async get(...args: any[]) { - this.response.body.path = [ - ['Hydro', 'homepage'], - ['contest_main', 'contest_main'], - [this.tdoc.title, 'contest_detail', { tid: args[1] }, true], - [this.pdoc.title, null, null, true], - ]; - // Navigate to current additional file download - // e.g. ![img](a.jpg) will navigate to ![img](./pid/file/a.jpg) - this.response.body.pdoc.content = this.response.body.pdoc.content - .replace(/\(file:\/\//g, `(./${args[2]}/file/`) - .replace(/="file:\/\//g, `="./${args[2]}/file/`); - } -} - -export class ContestProblemFileDownloadHandler extends ContestProblemHandler { - @param('filename', Types.Name) - @param('noDisposition', Types.Boolean) - async get(domainId: string, filename: string, noDisposition = false) { - if (this.pdoc.reference) { - this.pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); - if (!this.pdoc) throw new ProblemNotFoundError(); - } - const target = `problem/${this.pdoc.domainId}/${this.pdoc.docId}/additional_file/${filename}`; - const file = await storage.getMeta(target); - if (!file) { - this.response.redirect = await storage.signDownloadLink( - target, noDisposition ? undefined : filename, false, 'user', - ); - return; - } - const type = lookup(filename).toString(); - const shouldProxy = ['image', 'video', 'audio', 'pdf', 'vnd'].filter((i) => type.includes(i)).length; - if (shouldProxy && file.size! < 32 * 1024 * 1024) { - this.response.etag = file.etag; - this.response.body = await storage.get(target); - this.response.type = file['Content-Type'] || type; - this.response.disposition = `attachment; filename=${encodeURIComponent(filename)}`; - } else { - this.response.redirect = await storage.signDownloadLink( - target, noDisposition ? undefined : filename, false, 'user', - ); - } - } -} - -export class ContestDetailProblemSubmitHandler extends ContestProblemHandler { - @param('pid', Types.Name) - async prepare() { - if (!this.attended) throw new ContestNotAttendedError(this.tdoc.docId); - if (!contest.isOngoing(this.tdoc)) throw new ContestNotLiveError(this.tdoc.docId); - if (this.domain.langs) { - this.response.body.pdoc.config.langs = intersection( - this.response.body.pdoc.config.langs || this.domain.langs.split(','), - this.domain.langs.split(','), - ); - } - } - - @param('tid', Types.ObjectID) - @param('pid', Types.Name) - async get(domainId: string, tid: ObjectID, pid: string) { - this.response.body.path = [ - ['Hydro', 'homepage'], - ['contest_main', 'contest_main'], - [this.tdoc.title, 'contest_detail', { tid }, true], - [this.pdoc.title, 'contest_detail_problem', { tid, pid }, true], - ['contest_detail_problem_submit', null], - ]; - this.response.body.page_name = 'contest_detail_problem_submit'; - this.response.template = 'problem_submit.html'; - } - - @param('tid', Types.ObjectID) - @param('lang', Types.Name) - @param('code', Types.Content) - @param('pid', Types.Name) - @param('pretest', Types.Boolean) - @param('input', Types.String, true) - async post(domainId: string, tid: ObjectID, lang: string, code: string, _pid: string, pretest = false, input = '') { - const pid = this.tdoc.pids[parseInt(_pid, 36) - 10]; - if (!(this.response.body.pdoc.config.langs || [lang]).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 : tid, !pretest); - const rdoc = await record.get(domainId, rid); - if (!rdoc) throw new RecordNotFoundError(domainId, rid); - if (!pretest) { - await Promise.all([ - (this.tsdoc.journal || []).filter((i) => i.pid === pid).length - && problem.inc(this.domainId, this.pdoc.docId, 'nSubmit', 1), - domain.incUserInDomain(domainId, this.user._id, 'nSubmit'), - contest.updateStatus(domainId, tid, this.user._id, rid, pid), - ]); - } - bus.broadcast('record/change', rdoc); - if (!pretest && !contest.canShowSelfRecord.call(this, this.tdoc)) { - this.response.body = { tid }; - this.response.redirect = this.url('contest_detail', { tid }); - } else { - this.response.body = { rid }; - this.response.redirect = this.url('record_detail', { rid }); - } - } -} - export class ContestCodeHandler extends Handler { @param('tid', Types.ObjectID) async get(domainId: string, tid: ObjectID) { @@ -428,9 +261,6 @@ export async function apply() { Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST); Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST); Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST); - Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST); - Route('contest_detail_problem_file_download', '/contest/:tid/p/:pid/file/:filename', ContestProblemFileDownloadHandler, PERM.PERM_VIEW_PROBLEM); - Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestDetailProblemSubmitHandler, PERM.PERM_VIEW_CONTEST); Route('contest_code', '/contest/:tid/code', ContestCodeHandler, PERM.PERM_VIEW_CONTEST); } diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index a543610e6..486fd7b46 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -1,28 +1,20 @@ import AdmZip from 'adm-zip'; import yaml from 'js-yaml'; -import { intersection } from 'lodash'; -import { lookup } from 'mime-types'; import moment from 'moment-timezone'; import { ObjectID } from 'mongodb'; import { Time } from '@hydrooj/utils/lib/utils'; import { - BadRequestError, ContestNotFoundError, HomeworkNotAttendedError, - HomeworkNotLiveError, ProblemNotFoundError, ValidationError, + ContestNotFoundError, HomeworkNotLiveError, ValidationError, } from '../error'; -import { - PenaltyRules, ProblemDoc, Tdoc, User, -} from '../interface'; +import { PenaltyRules } from '../interface'; import paginate from '../lib/paginate'; -import { PERM, PRIV, STATUS } from '../model/builtin'; +import { PERM, PRIV } from '../model/builtin'; import * as contest from '../model/contest'; import * as discussion from '../model/discussion'; -import domain from '../model/domain'; import problem from '../model/problem'; import record from '../model/record'; -import storage from '../model/storage'; import * as system from '../model/system'; import user from '../model/user'; -import * as bus from '../service/bus'; import { Handler, param, Route, Types, } from '../service/server'; @@ -59,9 +51,7 @@ class HomeworkDetailHandler extends Handler { ]); const psdict = {}; let rdict = {}; - let attended = false; if (tsdoc) { - attended = tsdoc.attend === 1; for (const pdetail of tsdoc.journal || []) { psdict[pdetail.pid] = pdetail; rdict[pdetail.rid] = { _id: pdetail.rid }; @@ -82,14 +72,9 @@ class HomeworkDetailHandler extends Handler { const uids = ddocs.map((ddoc) => ddoc.owner); uids.push(tdoc.owner); const udict = await user.getList(domainId, uids); - const index = tdoc.pids.map((i) => i.toString()); - for (const key in pdict) { - const i = (index.indexOf(key) + 10).toString(36).toUpperCase(); - if (i !== '9') pdict[key].pid = i; - } this.response.template = 'homework_detail.html'; this.response.body = { - tdoc, tsdoc, attended, udict, pdict, psdict, rdict, ddocs, page, dpcount, dcount, + tdoc, tsdoc, udict, pdict, psdict, rdict, ddocs, page, dpcount, dcount, }; } @@ -111,151 +96,6 @@ class HomeworkDetailHandler extends Handler { } } -class HomeworkDetailProblemHandler extends Handler { - tdoc: Tdoc<30>; - pdoc: ProblemDoc; - pid: number; - tsdoc: any; - udoc: User; - attended: boolean; - - @param('tid', Types.ObjectID) - @param('pid', Types.String) - async _prepare(domainId: string, tid: ObjectID, _pid: string) { - 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, this.pid), - contest.getStatus(domainId, tid, this.user._id), - ]); - if (!this.pdoc) throw new ProblemNotFoundError(domainId, this.pid); - if (this.pdoc.reference) { - const pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); - this.pdoc.config = pdoc.config; - } - this.pdoc.pid = _pid; - this.attended = this.tsdoc?.attend === 1; - this.response.body = { - tdoc: this.tdoc, - pdoc: this.pdoc, - tsdoc: this.tsdoc, - udoc: this.udoc, - attended: this.attended, - }; - } - - @param('tid', Types.ObjectID) - @param('pid', Types.Name) - async get(domainId: string, tid: ObjectID, pid: string) { - if (!contest.isDone(this.tdoc)) { - if (!this.attended) throw new HomeworkNotAttendedError(tid); - if (contest.isNotStarted(this.tdoc)) throw new HomeworkNotLiveError(tid); - } - this.response.template = 'problem_detail.html'; - this.response.body.page_name = 'homework_detail_problem'; - this.response.body.path = [ - ['Hydro', 'homepage'], - ['homework_main', 'homework_main'], - [this.tdoc.title, 'homework_detail', { tid }, true], - [this.pdoc.title, null, null, true], - ]; - // Navigate to current additional file download - // e.g. ![img](a.jpg) will navigate to ![img](./pid/file/a.jpg) - this.response.body.pdoc.content = this.response.body.pdoc.content - .replace(/\(file:\/\//g, `(./${pid}/file/`) - .replace(/="file:\/\//g, `="./${pid}/file/`); - } -} - -export class HomeworkProblemFileDownloadHandler extends HomeworkDetailProblemHandler { - @param('filename', Types.Name) - @param('noDisposition', Types.Boolean) - // @ts-ignore - async get(domainId: string, filename: string, noDisposition = false) { - if (this.pdoc.reference) { - this.pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); - if (!this.pdoc) throw new ProblemNotFoundError(); - } - const target = `problem/${this.pdoc.domainId}/${this.pdoc.docId}/additional_file/${filename}`; - const file = await storage.getMeta(target); - if (!file) { - this.response.redirect = await storage.signDownloadLink( - target, noDisposition ? undefined : filename, false, 'user', - ); - return; - } - const type = lookup(filename).toString(); - const shouldProxy = ['image', 'video', 'audio', 'pdf', 'vnd'].filter((i) => type.includes(i)).length; - if (shouldProxy && file.size! < 32 * 1024 * 1024) { - this.response.etag = file.etag; - this.response.body = await storage.get(target); - this.response.type = file['Content-Type'] || type; - this.response.disposition = `attachment; filename=${encodeURIComponent(filename)}`; - } else { - this.response.redirect = await storage.signDownloadLink( - target, noDisposition ? undefined : filename, false, 'user', - ); - } - } -} - -class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { - @param('tid', Types.ObjectID) - async prepare(domainId: string, tid: ObjectID) { - if (!this.attended) throw new HomeworkNotAttendedError(tid); - if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid); - if (this.domain.langs) { - this.response.body.pdoc.config.langs = intersection( - this.response.body.pdoc.config.langs || this.domain.langs.split(','), - this.domain.langs.split(','), - ); - } - } - - @param('tid', Types.ObjectID) - @param('pid', Types.String) - async get(domainId: string, tid: ObjectID, pid: string) { - this.response.body.path = [ - ['Hydro', 'homepage'], - ['homework_main', 'homework_main'], - [this.tdoc.title, 'homework_detail', { tid }, true], - [this.pdoc.title, 'homework_detail_problem', { tid, pid }, true], - ['homework_detail_problem_submit', null], - ]; - this.response.template = 'problem_submit.html'; - } - - @param('tid', Types.ObjectID) - @param('code', Types.Content) - @param('lang', Types.Name) - @param('pretest', Types.Boolean) - @param('input', Types.String, true) - 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, 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 === this.pid).length - && problem.inc(this.domainId, this.pdoc.docId, 'nSubmit', 1), - domain.incUserInDomain(domainId, this.user._id, 'nSubmit'), - contest.updateStatus(domainId, tid, this.user._id, rid, this.pid, STATUS.STATUS_WAITING), - ]); - } - bus.broadcast('record/change', rdoc); - this.response.body.tid = tid; - this.response.body.rid = rid; - if (pretest || contest.canShowSelfRecord.call(this, this.tdoc)) this.response.redirect = this.url('record_detail', { rid }); - else this.response.redirect = this.url('homework_detail', { tid }); - } -} - class HomeworkEditHandler extends Handler { @param('tid', Types.ObjectID, true) async get(domainId: string, tid: ObjectID) { @@ -406,12 +246,6 @@ export async function apply() { 'homework_scoreboard_download', '/homework/:tid/scoreboard/download/:ext', HomeworkScoreboardDownloadHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD, ); - Route('homework_detail_problem', '/homework/:tid/p/:pid', HomeworkDetailProblemHandler, PERM.PERM_VIEW_HOMEWORK); - Route( - 'homework_detail_problem_file_download', '/homework/:tid/p/:pid/file/:filename', - HomeworkProblemFileDownloadHandler, PERM.PERM_VIEW_PROBLEM, - ); - Route('homework_detail_problem_submit', '/homework/:tid/p/:pid/submit', HomeworkDetailProblemSubmitHandler, PERM.PERM_SUBMIT_PROBLEM); Route('homework_code', '/homework/:tid/code', HomeworkCodeHandler, PERM.PERM_VIEW_HOMEWORK); Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler); } diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 664cf2ac6..dbb0b55a5 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -5,12 +5,12 @@ import { lookup } from 'mime-types'; import { FilterQuery, ObjectID } from 'mongodb'; import { sortFiles } from '@hydrooj/utils/lib/utils'; import { - BadRequestError, ForbiddenError, NoProblemError, + BadRequestError, ContestNotAttendedError, ContestNotFoundError, ContestNotLiveError, ForbiddenError, NoProblemError, PermissionError, ProblemNotFoundError, SolutionNotFoundError, ValidationError, } from '../error'; import { - ProblemDoc, ProblemStatusDoc, User, + ProblemDoc, ProblemStatusDoc, Tdoc, User, } from '../interface'; import difficultyAlgorithm from '../lib/difficulty'; import paginate from '../lib/paginate'; @@ -208,26 +208,37 @@ export class ProblemRandomHandler extends ProblemHandler { export class ProblemDetailHandler extends ProblemHandler { pdoc: ProblemDoc; + tdoc?: Tdoc<30>; + tsdoc?: any; udoc: User; psdoc: ProblemStatusDoc; @route('pid', Types.Name, true, null, parsePid) - async _prepare(domainId: string, pid: number | string) { - this.response.template = 'problem_detail.html'; + @query('tid', Types.ObjectID, true) + async _prepare(domainId: string, pid: number | string, tid?: ObjectID) { this.pdoc = await problem.get(domainId, pid); if (!this.pdoc) throw new ProblemNotFoundError(domainId, pid); - if (this.pdoc.hidden && !this.user.own(this.pdoc)) { + if (tid) { + this.tdoc = await contest.get(domainId, tid); + if (!this.tdoc) throw new ContestNotFoundError(domainId, tid); + this.tsdoc = await contest.getStatus(domainId, tid, this.user._id); + this.pdoc.tag.length = 0; + const showAccept = contest.canShowScoreboard.call(this, this.tdoc, true); + if (!showAccept) this.pdoc.nAccept = 0; + if (contest.isNotStarted(this.tdoc)) throw new ContestNotLiveError(tid); + if (!contest.isDone(this.tdoc) && !this.tsdoc.attend) throw new ContestNotAttendedError(tid); + } else if (this.pdoc.hidden && !this.user.own(this.pdoc)) { this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); } + if (this.pdoc.reference) { + const pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); + this.pdoc.config = pdoc.config; + } await bus.serial('problem/get', this.pdoc, this); [this.psdoc, this.udoc] = await Promise.all([ problem.getStatus(domainId, this.pdoc.docId, this.user._id), user.getById(domainId, this.pdoc.owner), ]); - if (this.pdoc.reference) { - const pdoc = await problem.get(this.pdoc.reference.domainId, this.pdoc.reference.pid); - this.pdoc.config = pdoc.config; - } const [scnt, dcnt] = await Promise.all([ solution.count(domainId, { parentId: this.pdoc.docId }), discussion.count(domainId, { parentId: this.pdoc.docId }), @@ -239,7 +250,15 @@ export class ProblemDetailHandler extends ProblemHandler { title: this.pdoc.title, solutionCount: scnt, discussionCount: dcnt, + tdoc: this.tdoc, + tsdoc: this.tsdoc, + page_name: this.tdoc + ? this.tdoc.rule === 'homework' + ? 'homework_detail_problem' + : 'contest_detail_problem' + : 'problem_detail', }; + this.response.template = 'problem_detail.html'; this.extraTitleContent = this.pdoc.title; } @@ -252,10 +271,12 @@ export class ProblemDetailHandler extends ProblemHandler { .replace(/\(file:\/\//g, `(./${this.pdoc.docId}/file/`) .replace(/="file:\/\//g, `="./${this.pdoc.docId}/file/`); } - if (this.psdoc) { - this.response.body.rdoc = await record.get(this.domainId, this.psdoc.rid); + if (!this.response.body.tdoc) { + if (this.psdoc?.rid) { + this.response.body.rdoc = await record.get(this.domainId, this.psdoc.rid); + } + this.response.body.ctdocs = await contest.getRelated(this.domainId, this.pdoc.docId); } - this.response.body.ctdocs = await contest.getRelated(this.domainId, this.pdoc.docId); } @param('pid', Types.UnsignedInt) @@ -288,60 +309,59 @@ export class ProblemDetailHandler extends ProblemHandler { } export class ProblemSubmitHandler extends ProblemDetailHandler { - async get() { + async get(domainId: string, tid?: ObjectID) { this.response.template = 'problem_submit.html'; this.response.body = { pdoc: this.pdoc, udoc: this.udoc, title: this.pdoc.title, + page_name: this.tdoc + ? this.tdoc.rule === 'homework' + ? 'homework_detail_problem_submit' + : 'contest_detail_problem_submit' + : 'problem_submit', }; + if (tid && !contest.isOngoing(this.tdoc)) throw new ContestNotLiveError(this.tdoc.docId); if (this.domain.langs) { this.response.body.pdoc.config.langs = intersection( this.response.body.pdoc.config.langs || this.domain.langs.split(','), this.domain.langs.split(','), ); } - this.response.body.pdoc.config.domainId = this.domainId; } @param('lang', Types.Name) @param('code', Types.Content) @param('pretest', Types.Boolean) @param('input', Types.String, true) - async post(domainId: string, lang: string, code: string, pretest = false, input = '') { + @param('tid', Types.ObjectID, true) + async post(domainId: string, lang: string, code: string, pretest = false, input = '', tid?: ObjectID) { if (this.response.body.pdoc.config?.langs && !this.response.body.pdoc.config.langs.includes(lang)) { throw new BadRequestError('Language not allowed.'); } if (pretest && this.response.body.pdoc.config?.type !== 'default') throw new BadRequestError('unable to run pretest'); await this.limitRate('add_record', 60, system.get('limit.submission')); - const rid = await record.add(domainId, this.pdoc.docId, this.user._id, lang, code, true, pretest ? input : undefined); + const rid = await record.add(domainId, this.pdoc.docId, this.user._id, lang, code, true, pretest ? input : tid, !pretest); const rdoc = await record.get(domainId, rid); if (!pretest) { await Promise.all([ - this.psdoc?.rid ? Promise.resolve() : problem.inc(domainId, this.pdoc.docId, 'nSubmit', 1), + (this.tdoc + ? (this.tsdoc.journal || []).filter((i) => i.pid === this.pdoc.docId).length + : this.psdoc?.rid ? Promise.resolve() : problem.inc(domainId, this.pdoc.docId, 'nSubmit', 1) + ) && problem.inc(this.domainId, this.pdoc.docId, 'nSubmit', 1), problem.incStatus(domainId, this.pdoc.docId, this.user._id, 'nSubmit', 1), domain.incUserInDomain(domainId, this.user._id, 'nSubmit'), + tid && contest.updateStatus(domainId, tid, this.user._id, rid, this.pdoc.docId), ]); } bus.broadcast('record/change', rdoc); - this.response.body = { rid }; - this.response.redirect = this.url('record_detail', { rid }); - } -} - -export class ProblemPretestHandler extends ProblemDetailHandler { - @param('lang', Types.Name) - @param('code', Types.Content) - @param('input', Types.Content, true) - async post(domainId: string, lang: string, code: string, input = '') { - await this.limitRate('do_pretest', 60, system.get('limit.pretest')); - const rid = await record.add( - domainId, this.pdoc.docId, this.user._id, - lang, code, true, input, - ); - const rdoc = await record.get(domainId, rid); - bus.broadcast('record/change', rdoc); - this.response.body = { rid }; + if (tid && !pretest && !contest.canShowSelfRecord.call(this, this.tdoc)) { + this.response.body = { tid }; + this.response.redirect = this.url(this.tdoc.rule === 'homework' ? 'homework_detail' : 'contest_detail', { tid }); + } else { + this.response.body = { rid }; + this.response.redirect = this.url('record_detail', { rid }); + } } } @@ -524,7 +544,9 @@ export class ProblemFileDownloadHandler extends ProblemDetailHandler { export class ProblemSolutionHandler extends ProblemDetailHandler { @param('page', Types.PositiveInt, true) - async get(domainId: string, page = 1) { + @param('tid', Types.ObjectID, true) + async get(domainId: string, page = 1, tid?: ObjectID) { + if (tid) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_SOLUTION); this.response.template = 'problem_solution.html'; this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION); const [psdocs, pcount, pscount] = await paginate( @@ -627,7 +649,9 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { export class ProblemSolutionRawHandler extends ProblemDetailHandler { @param('psid', Types.ObjectID) @route('psrid', Types.ObjectID, true) - async get(domainId: string, psid: ObjectID, psrid?: ObjectID) { + @param('tid', Types.ObjectID, true) + async get(domainId: string, psid: ObjectID, psrid?: ObjectID, tid?: ObjectID) { + if (tid) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_SOLUTION); this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION); if (psrid) { const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid); diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 69fb5b41e..7aa88bea0 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -238,6 +238,7 @@ class RecordMainConnectionHandler extends ConnectionHandler { if (rdoc.domainId !== this.domainId) return; if (rdoc.contest && ![this.tid, '000000000000000000000000'].includes(rdoc.contest.toString())) return; } + if (this.tid && rdoc.contest?.toString() !== this.tid) return; if (this.pid && rdoc.pid !== this.pid) return; if (this.uid && rdoc.uid !== this.uid) return; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 002cdb4e1..72c6bcd23 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -103,7 +103,7 @@ const acm: ContestRule = { columns.push({ type: 'problem_detail', value: '#{0}'.format(i), - raw: (i + 9).toString(36).toUpperCase(), + raw: pid, }); } } @@ -203,7 +203,7 @@ const oi: ContestRule = { columns.push({ type: 'problem_detail', value: '#{0}'.format(i), - raw: (i + 9).toString(36).toUpperCase(), + raw: tdoc.pids[i - 1], }); } } @@ -371,7 +371,7 @@ const homework: ContestRule = { columns.push({ type: 'problem_detail', value: '#{0}'.format(i), - raw: (i + 9).toString(36).toUpperCase(), + raw: pid, }); } } diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index e2e034295..7454b7c4c 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.26.0", + "version": "4.26.1", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/templates/components/problem.html b/packages/ui-default/templates/components/problem.html index 00507c709..5df186850 100644 --- a/packages/ui-default/templates/components/problem.html +++ b/packages/ui-default/templates/components/problem.html @@ -2,10 +2,7 @@ {%- if not invalid %} {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }} {{ _('Host') }}: {{ user.render_inline(udict[tdoc.owner], badge=false) }} {{ tdoc['attend']|default(0) }} - {% if attended %} + {% if tsdoc.attend %} {{ _('Attended') }} {% endif %} @@ -37,7 +37,7 @@

{{ _('Problem') }}

- {% if attended or model.contest.isDone(tdoc) %} + {% if tsdoc.attend or model.contest.isDone(tdoc) %}
diff --git a/packages/ui-default/templates/contest_scoreboard.html b/packages/ui-default/templates/contest_scoreboard.html index 6c50da089..e8990d9ff 100644 --- a/packages/ui-default/templates/contest_scoreboard.html +++ b/packages/ui-default/templates/contest_scoreboard.html @@ -24,7 +24,7 @@ {%- for column in rows[0] -%}
{% if column.type == 'problem_detail' %} - {{ column.value }} + {{ column.value }} {% else %} {{ column.value }} {% endif %} diff --git a/packages/ui-default/templates/homework_detail.html b/packages/ui-default/templates/homework_detail.html index d51dec1da..281b82ee1 100644 --- a/packages/ui-default/templates/homework_detail.html +++ b/packages/ui-default/templates/homework_detail.html @@ -18,8 +18,7 @@ {% if model.contest.isNotStarted(tdoc) %} {{ nothing.render('This homework is not open and you cannot view problems.') }} - {% else %} - {% if attended or model.contest.isDone(tdoc) %} + {% elif tsdoc.attend or model.contest.isDone(tdoc) %}
@@ -65,8 +64,7 @@ {% else %} {{ nothing.render('Please claim the assignment to see the problems.') }} - {% endif %}{# attended or handler.is_done(tdoc) #} - {% endif %}{# handler.is_not_started(tdoc) #} + {% endif %}
diff --git a/packages/ui-default/templates/partials/contest_sidebar.html b/packages/ui-default/templates/partials/contest_sidebar.html index 6c8d25cb2..f471db7b9 100644 --- a/packages/ui-default/templates/partials/contest_sidebar.html +++ b/packages/ui-default/templates/partials/contest_sidebar.html @@ -6,7 +6,7 @@

{{ tdoc.title }}

- {% if attended or tdoc.attend %} + {% if tsdoc.attend %} {{ _('Attended') }} {% else %} {{ _('Not Attended') }} @@ -38,7 +38,7 @@ {{ _('View Contest') }} {% endif %} - {% if not (attended or tdoc.attended) and not model.contest.isDone(tdoc) %} + {% if not (tsdoc.attend) and not model.contest.isDone(tdoc) %} {% endif %} - {% if not attended and not model.contest.isDone(tdoc) %} + {% if not tsdoc.attend and not model.contest.isDone(tdoc) %} {% endif %} - {% if attended %} + {% if tsdoc.attend %} diff --git a/packages/ui-default/templates/partials/problem_sidebar.html b/packages/ui-default/templates/partials/problem_sidebar.html index 8bc871602..918a6e52e 100644 --- a/packages/ui-default/templates/partials/problem_sidebar.html +++ b/packages/ui-default/templates/partials/problem_sidebar.html @@ -1,15 +1,12 @@ {% if tdoc %} - {% if tdoc.docType == 60 %} - {% include "partials/problem_sidebar_homework.html" %} - {% if not owner_udoc %} + {% if not owner_udoc %} {% set owner_udoc = udoc %} - {% endif %} + {% endif %} + {% if tdoc.rule == 'homework' %} + {% include "partials/problem_sidebar_homework.html" %} {% include "partials/homework_sidebar.html" %} {% else %} {% include "partials/problem_sidebar_contest.html" %} - {% if not owner_udoc %} - {% set owner_udoc = udoc %} - {% endif %} {% include "partials/contest_sidebar.html" %} {% endif %} {% else %} diff --git a/packages/ui-default/templates/partials/problem_sidebar_contest.html b/packages/ui-default/templates/partials/problem_sidebar_contest.html index 360b7b48a..fe175c175 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_contest.html +++ b/packages/ui-default/templates/partials/problem_sidebar_contest.html @@ -1,6 +1,6 @@ {% import "components/user.html" as user with context %}
-{% if page_name != 'problem_detail' and page_name != 'contest_detail_problem' %} +{% if page_name != 'contest_detail_problem' %}

{{ pdoc['title'] }}

@@ -16,24 +16,24 @@ {{ _('Quit Scratchpad') }} (Alt+Q) {% else %} - {% endif %} - {% elif model.contest.isDone(tdoc) %} - {% endif %} {% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %} - - {% endif %} diff --git a/packages/ui-default/templates/partials/problem_sidebar_homework.html b/packages/ui-default/templates/partials/problem_sidebar_homework.html index 390d699ec..3dbed8728 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_homework.html +++ b/packages/ui-default/templates/partials/problem_sidebar_homework.html @@ -1,6 +1,6 @@ {% import "components/user.html" as user with context %}
-{% if page_name != 'problem_detail' and page_name != 'homework_detail_problem' %} +{% if page_name != 'homework_detail_problem' %}

{{ pdoc['title'] }}

@@ -16,25 +16,25 @@ {{ _('Quit Scratchpad') }} (Alt+Q) {% else %} - {% endif %} - {% elif model.contest.isDone(tdoc) %} - {% endif %} {% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %} - - {% endif %} diff --git a/packages/ui-default/templates/partials/problem_sidebar_normal.html b/packages/ui-default/templates/partials/problem_sidebar_normal.html index c9b733dc6..cb120de06 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_normal.html +++ b/packages/ui-default/templates/partials/problem_sidebar_normal.html @@ -84,11 +84,18 @@ {{ _('Edit') }} {% endif %} - {% if page_name == 'problem_detail' and (handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA)) %} + {% if page_name == 'problem_detail' %} - + {% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %} + + {% endif %} + {% if handler.user.user.hasPriv(PRIV.PRIV_CREATE_DOMAIN) %} + + {% endif %} {% endif %}
diff --git a/packages/ui-default/templates/problem_detail.html b/packages/ui-default/templates/problem_detail.html index 3bd7e29e3..faa5d43b7 100644 --- a/packages/ui-default/templates/problem_detail.html +++ b/packages/ui-default/templates/problem_detail.html @@ -10,8 +10,8 @@ }) }} {% if tdoc %} {{ set(UiContext, 'canViewRecord', model.contest.canShowSelfRecord.call(handler, tdoc)) }} - {{ set(UiContext, 'postSubmitUrl', url('contest_detail_problem_submit' if tdoc.docType == model.document.TYPE_CONTEST else 'homework_detail_problem_submit', pid=pdoc.pid, tid=tdoc.docId)) }} - {{ set(UiContext, 'getSubmissionsUrl', url('record_main', query={fullStatus:'true', pid:pdoc.pid, tid:tdoc.docId})) }} + {{ set(UiContext, 'postSubmitUrl', url('problem_submit', pid=pdoc.docId, query={tid:tdoc.docId})) }} + {{ set(UiContext, 'getSubmissionsUrl', url('record_main', query={fullStatus:'true', pid:pdoc.docId, tid:tdoc.docId})) }} {{ set(UiContext, 'getRecordDetailUrl', url('record_detail', rid='{rid}', query={tid:tdoc.docId})) }} {{ set(UiContext, 'pretestConnUrl', "/record-conn?pretest=1&uidOrName=" + handler.user._id + "&pid=" + pdoc.docId + "&domainId=" + handler.domainId + "&tid=" + tdoc.docId) }} {{ set(UiContext, 'tdoc', tdoc) }} diff --git a/packages/ui-default/templates/record_detail.html b/packages/ui-default/templates/record_detail.html index 4a567370c..310706f9c 100644 --- a/packages/ui-default/templates/record_detail.html +++ b/packages/ui-default/templates/record_detail.html @@ -45,32 +45,26 @@
{% endif %} -
+
{{ _('Submit By') }}
{{ user.render_inline(udoc, modbadge=false) }}
{% if pdoc %}
{{ _('Problem') }}
- {{ problem.render_problem_title(pdoc, tdoc, show_tags=false, show_invisible_flag=false, rp=none) }} + {{ problem.render_problem_title(pdoc, tdoc, show_tags=false, show_invisible_flag=false) }}
{% endif %} {% if tdoc %} -
{{ _('Contest') if tdoc.docType == model.document.TYPE_CONTEST else _('Homework') }}
+
{{ _('Homework') if tdoc.rule == 'homework' else _('Contest') }}
- {{ tdoc.title }} + {{ tdoc.title }}
{% endif %}
{{ _('Language') }}
-
- {{ model.setting.langs[rdoc.lang].display }} -
-
- {{ _('Submit At') }} -
-
- {{ datetimeSpan(rdoc._id)|safe }} -
+
{{ model.setting.langs[rdoc.lang].display }}
+
{{ _('Submit At') }}
+
{{ datetimeSpan(rdoc._id)|safe }}
{% if rdoc.judgeAt %}
{{ _('Judged At') }}
{{ datetimeSpan(rdoc.judgeAt)|safe }}