mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: fix training progress calc (#277)
This commit is contained in:
1 parent
0b2a43d6e0
commit
354d6420d9
3 files changed
+31
-96
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "3.2.33",
|
"version": "3.2.34",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "src/loader",
|
"main": "src/loader",
|
||||||
"module": "src/loader",
|
"module": "src/loader",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { FilterQuery, ObjectID } from 'mongodb';
|
|||||||
import { ProblemNotFoundError, ValidationError } from '../error';
|
import { ProblemNotFoundError, ValidationError } from '../error';
|
||||||
import { Tdoc, TrainingDoc } from '../interface';
|
import { Tdoc, TrainingDoc } from '../interface';
|
||||||
import paginate from '../lib/paginate';
|
import paginate from '../lib/paginate';
|
||||||
import { isContent, isDescription } from '../lib/validator';
|
|
||||||
import { PERM, PRIV } from '../model/builtin';
|
import { PERM, PRIV } from '../model/builtin';
|
||||||
import * as builtin from '../model/builtin';
|
import * as builtin from '../model/builtin';
|
||||||
import problem from '../model/problem';
|
import problem from '../model/problem';
|
||||||
@@ -21,13 +20,14 @@ async function _parseDagJson(domainId: string, _dag: string): Promise<Tdoc['dag'
|
|||||||
const dag = JSON.parse(_dag);
|
const dag = JSON.parse(_dag);
|
||||||
assert(dag instanceof Array, 'dag must be an array');
|
assert(dag instanceof Array, 'dag must be an array');
|
||||||
const ids = new Set(dag.map((s) => s._id));
|
const ids = new Set(dag.map((s) => s._id));
|
||||||
|
assert(dag.length, 'must have at least one node');
|
||||||
assert(dag.length === ids.size, '_id must be unique');
|
assert(dag.length === ids.size, '_id must be unique');
|
||||||
for (const node of dag) {
|
for (const node of dag) {
|
||||||
assert(node._id, 'each node should have a _id');
|
assert(node._id, 'each node should have a _id');
|
||||||
assert(node.title, 'each node shoule have a title');
|
assert(node.title, 'each node shoule have a title');
|
||||||
assert(node.requireNids instanceof Array);
|
assert(node.requireNids instanceof Array);
|
||||||
assert(node.pids instanceof Array);
|
assert(node.pids instanceof Array);
|
||||||
assert(node.pids.length);
|
assert(node.pids.length, 'each node must contain at lease one problem');
|
||||||
for (const nid of node.requireNids) {
|
for (const nid of node.requireNids) {
|
||||||
assert(ids.has(nid), `required nid ${nid} not found`);
|
assert(ids.has(nid), `required nid ${nid} not found`);
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ async function _parseDagJson(domainId: string, _dag: string): Promise<Tdoc['dag'
|
|||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
const newNode = {
|
const newNode = {
|
||||||
_id: parseInt(node._id, 10),
|
_id: +node._id,
|
||||||
title: node.title,
|
title: node.title,
|
||||||
requireNids: Array.from(new Set(node.requireNids)),
|
requireNids: Array.from(new Set(node.requireNids)),
|
||||||
pids: Array.from(new Set(node.pids)),
|
pids: Array.from(new Set(node.pids)),
|
||||||
@@ -84,13 +84,9 @@ class TrainingMainHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const tdoc of tdocs) tdict[tdoc.docId.toHexString()] = tdoc;
|
for (const tdoc of tdocs) tdict[tdoc.docId.toHexString()] = tdoc;
|
||||||
const path = [
|
|
||||||
['Hydro', 'homepage'],
|
|
||||||
['training_main', null],
|
|
||||||
];
|
|
||||||
this.response.template = 'training_main.html';
|
this.response.template = 'training_main.html';
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
tdocs, page, tpcount, tsdict, tdict, path,
|
tdocs, page, tpcount, tsdict, tdict,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,6 +105,7 @@ class TrainingDetailHandler extends Handler {
|
|||||||
const donePids = new Set<number>();
|
const donePids = new Set<number>();
|
||||||
const progPids = new Set<number>();
|
const progPids = new Set<number>();
|
||||||
for (const pid in psdict) {
|
for (const pid in psdict) {
|
||||||
|
if (!+pid) continue;
|
||||||
const psdoc = psdict[pid];
|
const psdoc = psdict[pid];
|
||||||
if (psdoc.status) {
|
if (psdoc.status) {
|
||||||
if (psdoc.status === builtin.STATUS.STATUS_ACCEPTED) {
|
if (psdoc.status === builtin.STATUS.STATUS_ACCEPTED) {
|
||||||
@@ -138,14 +135,9 @@ class TrainingDetailHandler extends Handler {
|
|||||||
donePids: Array.from(donePids),
|
donePids: Array.from(donePids),
|
||||||
done: doneNids.size === tdoc.dag.length,
|
done: doneNids.size === tdoc.dag.length,
|
||||||
});
|
});
|
||||||
const path = [
|
|
||||||
['Hydro', 'homepage'],
|
|
||||||
['training_main', 'training_main'],
|
|
||||||
[tdoc.title, null, null, true],
|
|
||||||
];
|
|
||||||
this.response.template = 'training_detail.html';
|
this.response.template = 'training_detail.html';
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
path, tdoc, tsdoc, pids, pdict, psdict, ndict, nsdict, owner,
|
tdoc, tsdoc, pids, pdict, psdict, ndict, nsdict, owner,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,103 +158,50 @@ class TrainingDetailHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrainingCreateHandler extends Handler {
|
|
||||||
async get() {
|
|
||||||
const path = [
|
|
||||||
['Hydro', 'homepage'],
|
|
||||||
['training_main', 'training_main'],
|
|
||||||
['training_create', null],
|
|
||||||
];
|
|
||||||
this.response.template = 'training_edit.html';
|
|
||||||
this.response.body = { page_name: 'training_create', path };
|
|
||||||
}
|
|
||||||
|
|
||||||
@param('title', Types.Title)
|
|
||||||
@param('content', Types.Content)
|
|
||||||
@param('dag', Types.String, isContent)
|
|
||||||
@param('pin', Types.Boolean)
|
|
||||||
@param('description', Types.String, isDescription)
|
|
||||||
async post(
|
|
||||||
domainId: string, title: string, content: string,
|
|
||||||
_dag: string, pin = false, description: string,
|
|
||||||
) {
|
|
||||||
if (pin) this.checkPerm(PERM.PERM_PIN_TRAINING);
|
|
||||||
const dag = await _parseDagJson(domainId, _dag);
|
|
||||||
const pids = training.getPids(dag);
|
|
||||||
assert(pids.length, new ValidationError('dag', null, 'Please specify at least one problem'));
|
|
||||||
const pdocs = await problem.getMulti(domainId, { docId: { $in: pids } }).sort('_id', 1).toArray();
|
|
||||||
for (const pdoc of pdocs) {
|
|
||||||
if (pdoc.hidden) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
|
|
||||||
}
|
|
||||||
const tid = await training.add(domainId, title, content, this.user._id, dag, description);
|
|
||||||
if (pin) await training.edit(domainId, tid, { pin });
|
|
||||||
this.response.body = { tid };
|
|
||||||
this.response.redirect = this.url('training_detail', { tid });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrainingEditHandler extends Handler {
|
class TrainingEditHandler extends Handler {
|
||||||
tdoc: TrainingDoc;
|
tdoc: TrainingDoc;
|
||||||
|
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID, true)
|
||||||
async prepare(domainId: string, tid: ObjectID) {
|
async prepare(domainId: string, tid: ObjectID) {
|
||||||
this.tdoc = await training.get(domainId, tid);
|
if (tid) {
|
||||||
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING);
|
this.tdoc = await training.get(domainId, tid);
|
||||||
else this.checkPerm(PERM.PERM_EDIT_TRAINING_SELF);
|
if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING);
|
||||||
|
else this.checkPerm(PERM.PERM_EDIT_TRAINING_SELF);
|
||||||
|
} else this.checkPerm(PERM.PERM_CREATE_TRAINING);
|
||||||
}
|
}
|
||||||
|
|
||||||
async get({ tid }) {
|
async get() {
|
||||||
const dag = JSON.stringify(this.tdoc.dag, null, 2);
|
|
||||||
const path = [
|
|
||||||
['Hydro', 'homepage'],
|
|
||||||
['training_main', 'training_main'],
|
|
||||||
[this.tdoc.title, 'training_detail', { tid }, true],
|
|
||||||
['training_edit', null],
|
|
||||||
];
|
|
||||||
this.response.template = 'training_edit.html';
|
this.response.template = 'training_edit.html';
|
||||||
this.response.body = {
|
this.response.body = { page_name: this.tdoc ? 'training_edit' : 'training_create' };
|
||||||
tdoc: this.tdoc, dag, path, page_name: 'training_edit',
|
if (this.tdoc) {
|
||||||
};
|
this.response.body.tdoc = this.tdoc;
|
||||||
|
this.response.body.tag = JSON.stringify(this.tdoc.dag, null, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID, true)
|
||||||
@param('title', Types.Title)
|
@param('title', Types.Title)
|
||||||
@param('content', Types.Content)
|
@param('content', Types.Content)
|
||||||
@param('dag', Types.Content)
|
@param('dag', Types.Content)
|
||||||
@param('pin', Types.Boolean)
|
@param('pin', Types.Boolean)
|
||||||
@param('description', Types.String, isDescription)
|
@param('description', Types.Content)
|
||||||
async post(
|
async post(
|
||||||
domainId: string, tid: ObjectID,
|
domainId: string, tid: ObjectID,
|
||||||
title: string, content: string,
|
title: string, content: string,
|
||||||
_dag: string, pin = false, description: string,
|
_dag: string, pin = false, description: string,
|
||||||
) {
|
) {
|
||||||
if ((!!this.tdoc.pin) !== pin) this.checkPerm(PERM.PERM_PIN_TRAINING);
|
if ((!!this.tdoc?.pin) !== pin) this.checkPerm(PERM.PERM_PIN_TRAINING);
|
||||||
const dag = await _parseDagJson(domainId, _dag);
|
const dag = await _parseDagJson(domainId, _dag);
|
||||||
const pids = training.getPids(dag);
|
const pids = training.getPids(dag);
|
||||||
assert(pids.length, new ValidationError('dag'));
|
assert(pids.length, new ValidationError('dag', null, 'Please specify at least one problem'));
|
||||||
const pdocs = await problem.getMulti(domainId, {
|
if (!tid) {
|
||||||
$or: [
|
tid = await training.add(domainId, title, content, this.user._id, dag, description);
|
||||||
{ docId: { $in: pids } },
|
if (pin) await training.edit(domainId, tid, { pin });
|
||||||
// TODO
|
} else {
|
||||||
// @ts-ignore
|
await training.edit(domainId, tid, {
|
||||||
{ pid: { $in: pids } },
|
title, content, dag, description, pin,
|
||||||
],
|
});
|
||||||
}).sort('_id', 1).toArray();
|
|
||||||
const existPids = pdocs.map((pdoc) => pdoc.docId);
|
|
||||||
if (pids.length !== existPids.length) {
|
|
||||||
for (const pid in pids) {
|
|
||||||
assert(
|
|
||||||
existPids.includes(parseInt(pid, 10)),
|
|
||||||
new ProblemNotFoundError(pid),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (const pdoc of pdocs) {
|
|
||||||
if (pdoc.hidden) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
|
|
||||||
}
|
|
||||||
await training.edit(domainId, tid, {
|
|
||||||
title, content, dag, description, pin,
|
|
||||||
});
|
|
||||||
this.response.body = { tid };
|
this.response.body = { tid };
|
||||||
this.response.redirect = this.url('training_detail', { tid });
|
this.response.redirect = this.url('training_detail', { tid });
|
||||||
}
|
}
|
||||||
@@ -270,7 +209,7 @@ class TrainingEditHandler extends Handler {
|
|||||||
|
|
||||||
export async function apply() {
|
export async function apply() {
|
||||||
Route('training_main', '/training', TrainingMainHandler, PERM.PERM_VIEW_TRAINING);
|
Route('training_main', '/training', TrainingMainHandler, PERM.PERM_VIEW_TRAINING);
|
||||||
Route('training_create', '/training/create', TrainingCreateHandler, PERM.PERM_CREATE_TRAINING);
|
Route('training_create', '/training/create', TrainingEditHandler);
|
||||||
Route('training_detail', '/training/:tid', TrainingDetailHandler, PERM.PERM_VIEW_TRAINING);
|
Route('training_detail', '/training/:tid', TrainingDetailHandler, PERM.PERM_VIEW_TRAINING);
|
||||||
Route('training_edit', '/training/:tid/edit', TrainingEditHandler);
|
Route('training_edit', '/training/:tid/edit', TrainingEditHandler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ export const isPid = (s) => RE_PID.test(s.toString());
|
|||||||
export const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
export const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
||||||
export const isIntro = () => true;
|
export const isIntro = () => true;
|
||||||
export const checkIntro = (s) => { if (!isIntro()) throw new ValidationError('intro'); else return s; };
|
export const checkIntro = (s) => { if (!isIntro()) throw new ValidationError('intro'); else return s; };
|
||||||
export const isDescription = (s: any) => s && s.length < 65536;
|
|
||||||
export const checkDescription = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('description'); else return s; };
|
|
||||||
|
|
||||||
export const parsePid = (s) => (Number.isNaN(+s) ? s : `P${s}`);
|
export const parsePid = (s) => (Number.isNaN(+s) ? s : `P${s}`);
|
||||||
|
|
||||||
@@ -110,8 +108,6 @@ global.Hydro.lib.validator = {
|
|||||||
checkPid,
|
checkPid,
|
||||||
isIntro,
|
isIntro,
|
||||||
checkIntro,
|
checkIntro,
|
||||||
isDescription,
|
|
||||||
checkDescription,
|
|
||||||
|
|
||||||
parsePid,
|
parsePid,
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user