core: 删除题目前检查是否已被比赛使用

pull/148/head
undefined 3 years ago
parent e4c6cf258d
commit 26ded9d5df

@ -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",

@ -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 });

@ -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';

@ -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();

@ -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);

@ -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,

@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.8.32",
"version": "4.8.33",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",

@ -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({

@ -105,14 +105,14 @@
</ol>
</div>
</div>
{% 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' %}
<div class="section side">
<div class="section__header">
<h1 class="section__title">{{ _('Information') }}</h1>
</div>
{% include "partials/problem-sidebar-information.html" %}
</div>
{% else %}
{% elif page_name != 'contest_detail_problem' %}
<div class="section side nojs-hide">
<div class="section__header">
<h1 class="section__title">{{ _('Statistics') }}</h1>
@ -121,22 +121,22 @@
<div id="submission-score-placeholder" style="width: 100%; height:230px"></div>
</div>
{% endif %}
{% if page_name == 'problem_detail' and (tdocs or ctdocs) %}
{% if page_name == 'problem_detail' and (tdocs.length or ctdocs.length) %}
<div class="section side">
<div class="section__header">
<h1 class="section__title">{{ _('Related') }}</h1>
</div>
<div class="section__body typo">
{% if tdocs %}
{% if tdocs.length %}
<p>{{ _('In following training plans') }}: </p>
{%- for tdoc in tdocs -%}
<p><a href="{{ url('training_detail', tid=tdoc.docId) }}">{{ tdoc.title }}</a></p>
<p><a href="{{ url('training_detail', tid=tdoc.docId) }}">{{ tdoc.title }}</a></p>
{%- endfor -%}
{% endif %}
{% if ctdocs %}
{% if ctdocs.length %}
<p>{{ _('In following contests') }}: </p>
{%- for tdoc in ctdocs -%}
<p><a href="{{ url('contest_detail', tid=tdoc.docId) }}">{{ tdoc.title }}</a></p>
<p><a href="{{ url('contest_detail', tid=tdoc.docId) }}">{{ tdoc.title }}</a></p>
{%- endfor -%}
{% endif %}
</div>

Loading…
Cancel
Save