mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
core: 删除题目前检查是否已被比赛使用
This commit is contained in:
1 parent
e4c6cf258d
commit
26ded9d5df
9 files changed
+78
-62
No files matched your search
@@ -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.  will navigate to 
|
||||
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>
|
||||
|
||||
Reference in new issue
Block a user