From 26ded9d5df0ac7c39620e02ddacc447399dfbc4c Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 20 May 2021 22:55:47 +0800 Subject: [PATCH] =?UTF-8?q?core:=20=E5=88=A0=E9=99=A4=E9=A2=98=E7=9B=AE?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=9F=A5=E6=98=AF=E5=90=A6=E5=B7=B2=E8=A2=AB?= =?UTF-8?q?=E6=AF=94=E8=B5=9B=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/contest.ts | 25 +++---- packages/hydrooj/src/handler/discussion.ts | 1 - packages/hydrooj/src/handler/homework.ts | 69 ++++++++++--------- packages/hydrooj/src/handler/problem.ts | 17 ++++- packages/hydrooj/src/model/contest.ts | 5 ++ packages/ui-default/package.json | 2 +- .../ui-default/pages/problem_detail.page.jsx | 5 +- .../partials/problem_sidebar_normal.html | 14 ++-- 9 files changed, 78 insertions(+), 62 deletions(-) diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index baa5fc974..2355afd32 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.26.18", + "version": "2.26.19", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index e0649b696..cf3557a32 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -1,7 +1,6 @@ import moment from 'moment-timezone'; import { ObjectID } from 'mongodb'; import AdmZip from 'adm-zip'; -import { isSafeInteger } from 'lodash'; import { ContestNotLiveError, ValidationError, ProblemNotFoundError, ContestNotAttendedError, PermissionError, BadRequestError, @@ -215,11 +214,8 @@ class ContestEditHandler extends Handler { domainId: string, beginAtDate: string, beginAtTime: string, duration: number, title: string, content: string, rule: string, _pids: string, rated = false, ) { - const pids = _pids.replace(/,/g, ',').split(',').map((i) => { - if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10); - return i; - }); - await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), false); + const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i); + await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), true); const beginAtMoment = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone); if (!beginAtMoment.isValid()) { throw new ValidationError('beginAtDate', 'beginAtTime'); @@ -389,7 +385,7 @@ class ContestCodeHandler extends Handler { for (const rdoc of rdocs) { zip.addFile(`${rnames[rdoc._id.toHexString()]}.${rdoc.lang}`, Buffer.from(rdoc.code)); } - await this.binary(zip.toBuffer(), `${tdoc.title}.zip`); + this.binary(zip.toBuffer(), `${tdoc.title}.zip`); } } @@ -429,19 +425,14 @@ class ContestCreateHandler extends Handler { domainId: string, beginAtDate: string, beginAtTime: string, duration: number, title: string, content: string, rule: string, _pids: string, rated = false, ) { - const pids = _pids.replace(/,/g, ',').split(',').map((i) => { - if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10); - return i; - }); + const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i); const beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone); - if (!beginAt.isValid()) { - throw new ValidationError('beginAtDate', 'beginAtTime'); - } - const endAt = beginAt.clone().add(duration, 'hours'); - await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), false); + if (!beginAt.isValid()) throw new ValidationError('beginAtDate', 'beginAtTime'); + const endAt = beginAt.clone().add(duration, 'hours').toDate(); + await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), true); const tid = await contest.add( domainId, title, content, - this.user._id, rule, beginAt.toDate(), endAt.toDate(), pids, rated, + this.user._id, rule, beginAt.toDate(), endAt, pids, rated, ); this.response.body = { tid }; this.response.redirect = this.url('contest_detail', { tid }); diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index f9c621ff9..d5dae1aaa 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -6,7 +6,6 @@ import paginate from '../lib/paginate'; import * as system from '../model/system'; import user from '../model/user'; import * as oplog from '../model/oplog'; -import message from '../model/message'; import * as discussion from '../model/discussion'; import * as document from '../model/document'; import { PERM, PRIV } from '../model/builtin'; diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 357f488a2..8a0ac9348 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -2,12 +2,14 @@ import yaml from 'js-yaml'; import moment from 'moment-timezone'; import { ObjectID } from 'mongodb'; import AdmZip from 'adm-zip'; -import { isSafeInteger } from 'lodash'; import { ValidationError, HomeworkNotLiveError, ProblemNotFoundError, HomeworkNotAttendedError, + BadRequestError, } from '../error'; -import { PenaltyRules, Tdoc, ProblemDoc } from '../interface'; +import { + PenaltyRules, Tdoc, ProblemDoc, User, +} from '../interface'; import { Route, Handler, Types, param, } from '../service/server'; @@ -22,6 +24,7 @@ import problem from '../model/problem'; import record from '../model/record'; import * as document from '../model/document'; import paginate from '../lib/paginate'; +import { parseConfig } from '../lib/testdataConfig'; const validatePenaltyRules = (input: string) => { const s = yaml.load(input); @@ -120,6 +123,8 @@ class HomeworkDetailProblemHandler extends Handler { tdoc: Tdoc<60>; pdoc: ProblemDoc; tsdoc: any; + udoc: User; + attended: boolean; @param('tid', Types.ObjectID) @param('pid', Types.UnsignedInt) @@ -130,28 +135,38 @@ class HomeworkDetailProblemHandler extends Handler { problem.get(domainId, pid, this.user._id), contest.getStatus(domainId, tid, this.user._id, document.TYPE_HOMEWORK), ]); + this.udoc = await user.getById(domainId, this.tdoc.owner); + this.attended = this.tsdoc && this.tsdoc.attend === 1; + this.response.body = { + tdoc: this.tdoc, + pdoc: this.pdoc, + tsdoc: this.tsdoc, + udoc: this.udoc, + attended: this.attended, + }; + try { + this.response.body.pdoc.config = await parseConfig(this.pdoc.config); + } catch (e) { + this.response.body.pdoc.config = `Cannot parse: ${e.message}`; + } } @param('tid', Types.ObjectID) @param('pid', Types.UnsignedInt) async get(domainId: string, tid: ObjectID, pid: number) { - const udoc = await user.getById(domainId, this.tdoc.owner); - const attended = this.tsdoc && this.tsdoc.attend === 1; if (!contest.isDone(this.tdoc)) { - if (!attended) throw new HomeworkNotAttendedError(tid); + if (!this.attended) throw new HomeworkNotAttendedError(tid); if (contest.isNotStarted(this.tdoc)) throw new HomeworkNotLiveError(tid); } if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid, tid); - const path = [ + 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], ]; - this.response.template = 'problem_detail.html'; - this.response.body = { - tdoc: this.tdoc, pdoc: this.pdoc, tsdoc: this.tsdoc, udoc, attended, path, page_name: 'homework_detail_problem', - }; } } @@ -159,16 +174,14 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { @param('tid', Types.ObjectID) @param('pid', Types.UnsignedInt) async get(domainId: string, tid: ObjectID, pid: number) { - const udoc = await user.getById(domainId, this.tdoc.owner); - const attended = this.tsdoc && this.tsdoc.attend === 1; - if (!attended) throw new HomeworkNotAttendedError(tid); + if (!this.attended) throw new HomeworkNotAttendedError(tid); if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid); if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid, tid); - const rdocs = contest.canShowRecord.call(this, this.tdoc) + this.response.body.rdocs = contest.canShowRecord.call(this, this.tdoc) ? await record.getUserInProblemMulti(domainId, this.user._id, pid, true) .sort('_id', -1).limit(10).toArray() : []; - const path = [ + this.response.body.path = [ ['Hydro', 'homepage'], ['homework_main', 'homework_main'], [this.tdoc.title, 'homework_detail', { tid }, true], @@ -176,9 +189,6 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { ['homework_detail_problem_submit', null], ]; this.response.template = 'problem_submit.html'; - this.response.body = { - tdoc: this.tdoc, pdoc: this.pdoc, tsdoc: this.tsdoc, udoc, attended, path, rdocs, - }; } @param('tid', Types.ObjectID) @@ -186,9 +196,11 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { @param('code', Types.Content) @param('lang', Types.Name) async post(domainId: string, tid: ObjectID, pid: number, code: string, lang: string) { + 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, 5); - const tsdoc = await contest.getStatus(domainId, tid, this.user._id, document.TYPE_HOMEWORK); - if (!tsdoc.attend) throw new HomeworkNotAttendedError(tid); + if (!this.attended) throw new HomeworkNotAttendedError(tid); if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid); if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid); const rid = await record.add(domainId, pid, this.user._id, lang, code, true, { @@ -203,7 +215,8 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { rid, pid, false, 0, document.TYPE_HOMEWORK), ]); bus.boardcast('record/change', rdoc); - this.response.body = { tid, rid }; + this.response.body.tid = tid; + this.response.body.rid = rid; if (contest.canShowRecord.call(this, this.tdoc)) this.response.redirect = this.url('record_detail', { rid }); else this.response.redirect = this.url('homework_detail', { tid }); } @@ -246,10 +259,7 @@ class HomeworkCreateHandler extends Handler { penaltySinceDate: string, penaltySinceTime: string, extensionDays: number, penaltyRules: PenaltyRules, title: string, content: string, _pids: string, rated = false, ) { - const pids = _pids.replace(/,/g, ',').split(',').map((i) => { - if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10); - return i; - }); + const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i); const beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone); if (!beginAt.isValid()) throw new ValidationError('beginAtDate', 'beginAtTime'); const penaltySince = moment.tz(`${penaltySinceDate} ${penaltySinceTime}`, this.user.timeZone); @@ -257,7 +267,7 @@ class HomeworkCreateHandler extends Handler { const endAt = penaltySince.clone().add(extensionDays, 'days'); if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime'); if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays'); - await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), false); + await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), true); const tid = await contest.add(domainId, title, content, this.user._id, 'homework', beginAt.toDate(), endAt.toDate(), pids, rated, { penaltySince: penaltySince.toDate(), penaltyRules }, document.TYPE_HOMEWORK); @@ -313,10 +323,7 @@ class HomeworkEditHandler extends Handler { penaltySinceDate: string, penaltySinceTime: string, extensionDays: number, penaltyRules: string, title: string, content: string, _pids: string, rated = false, ) { - const pids = _pids.replace(/,/g, ',').split(',').map((i) => { - if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10); - return i; - }); + const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i); const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK); if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK); else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF); @@ -334,7 +341,7 @@ class HomeworkEditHandler extends Handler { } let endAt = penaltySince.clone().add(extensionDays, 'days'); if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime'); - await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), false); + await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), true); beginAt = beginAt.toDate(); endAt = endAt.toDate(); penaltySince = penaltySince.toDate(); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 73892d4be..ee813cf89 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -19,6 +19,8 @@ import problem from '../model/problem'; import record from '../model/record'; import domain from '../model/domain'; import user from '../model/user'; +import * as document from '../model/document'; +import * as contest from '../model/contest'; import solution from '../model/solution'; import { PERM, PRIV } from '../model/builtin'; import storage from '../service/storage'; @@ -213,11 +215,14 @@ export class ProblemDetailHandler extends ProblemHandler { async get(..._args: any[]) { // 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, `(./${this.pdoc.docId}/file/`); + if (!this.request.json) { + this.response.body.pdoc.content = this.response.body.pdoc.content + .replace(/\(file:\/\//g, `(./${this.pdoc.docId}/file/`); + } if (this.pdoc.psdoc) { this.response.body.rdoc = await record.get(this.domainId, this.pdoc.psdoc.rid); } + this.response.body.ctdocs = await contest.getRelated(this.domainId, this.pdoc.docId); } @param('pid', Types.UnsignedInt) @@ -233,6 +238,12 @@ 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); await problem.del(this.pdoc.domainId, this.pdoc.docId); this.response.redirect = this.url('problem_main'); } @@ -264,7 +275,7 @@ export class ProblemSubmitHandler extends ProblemDetailHandler { @param('lang', Types.Name) @param('code', Types.Content) async post(domainId: string, lang: string, code: string) { - if (this.response.body.pdoc.config.langs && !this.response.body.pdoc.config.langs.includes('lang')) { + if (this.response.body.pdoc.config?.langs && !this.response.body.pdoc.config.langs.includes('lang')) { throw new BadRequestError('Language not allowed.'); } await this.limitRate('add_record', 60, 5); diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 7ddf045da..94c8584ef 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -444,6 +444,10 @@ export async function get(domainId: string, tid: ObjectID, type: Type | -1 = doc return tdoc; } +export async function getRelated(domainId: string, pid: number, type: Type = document.TYPE_CONTEST) { + return await document.getMulti(domainId, type, { pids: pid }).toArray(); +} + export async function updateStatus( domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number, accept = false, score = 0, type: 30 | 60 = document.TYPE_CONTEST, @@ -624,6 +628,7 @@ global.Hydro.model.contest = { edit, del, get, + getRelated, updateStatus, getStatus, count, diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 695d4b011..1a711bce9 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.32", + "version": "4.8.33", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/pages/problem_detail.page.jsx b/packages/ui-default/pages/problem_detail.page.jsx index 0a43007ad..4b0a64343 100644 --- a/packages/ui-default/pages/problem_detail.page.jsx +++ b/packages/ui-default/pages/problem_detail.page.jsx @@ -251,7 +251,10 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor loadSubjective(); if (pagename === 'contest_detail_problem') return; - UiContext.pdoc.stats = UiContext.pdoc.stats || {}; + if (!Object.keys(UiContext.pdoc.stats || {}).length) { + $('#submission-status-placeholder').parent().hide(); + return; + } const $status = document.getElementById('submission-status-placeholder'); const statusChart = echarts.init($status); statusChart.setOption({ diff --git a/packages/ui-default/templates/partials/problem_sidebar_normal.html b/packages/ui-default/templates/partials/problem_sidebar_normal.html index 86b562a1d..d205b00bd 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_normal.html +++ b/packages/ui-default/templates/partials/problem_sidebar_normal.html @@ -105,14 +105,14 @@ -{% if page_name != 'problem_detail' and page_name != 'homework_detail_problem' %} +{% if page_name != 'problem_detail' and page_name != 'homework_detail_problem' and page_name != 'contest_detail_problem' %}

{{ _('Information') }}

{% include "partials/problem-sidebar-information.html" %}
-{% else %} +{% elif page_name != 'contest_detail_problem' %}

{{ _('Statistics') }}

@@ -121,22 +121,22 @@
{% endif %} -{% if page_name == 'problem_detail' and (tdocs or ctdocs) %} +{% if page_name == 'problem_detail' and (tdocs.length or ctdocs.length) %}

{{ _('Related') }}

- {% if tdocs %} + {% if tdocs.length %}

{{ _('In following training plans') }}:

{%- for tdoc in tdocs -%} -

{{ tdoc.title }}

+

{{ tdoc.title }}

{%- endfor -%} {% endif %} - {% if ctdocs %} + {% if ctdocs.length %}

{{ _('In following contests') }}:

{%- for tdoc in ctdocs -%} -

{{ tdoc.title }}

+

{{ tdoc.title }}

{%- endfor -%} {% endif %}