You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/hydrooj/src/handler/problem.ts

701 lines
28 KiB
TypeScript

import { isSafeInteger, flatten, pick } from 'lodash';
4 years ago
import yaml from 'js-yaml';
import fs from 'fs-extra';
import { FilterQuery, ObjectID } from 'mongodb';
import AdmZip from 'adm-zip';
4 years ago
import {
NoProblemError, BadRequestError, PermissionError,
4 years ago
SolutionNotFoundError, ProblemNotFoundError, ValidationError,
} from '../error';
import { streamToBuffer } from '../utils';
import {
Pdoc, User, Rdoc, PathComponent,
} from '../interface';
import paginate from '../lib/paginate';
import { isTitle, isContent, isPid } from '../lib/validator';
import { ProblemAdd } from '../lib/ui';
import * as problem from '../model/problem';
import * as record from '../model/record';
import * as domain from '../model/domain';
import * as user from '../model/user';
import * as solution from '../model/solution';
import { PERM, PRIV, CONSTANT } from '../model/builtin';
import storage from '../service/storage';
import * as bus from '../service/bus';
import {
Route, Connection, Handler, ConnectionHandler, Types, param, post, route,
} from '../service/server';
export const parseCategory = (value: string) => flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim());
export const parsePid = (value: string) => (isSafeInteger(value) ? parseInt(value, 10) : value);
export class ProblemHandler extends Handler {
async __prepare() {
this.checkPerm(PERM.PERM_VIEW_PROBLEM);
5 years ago
}
5 years ago
async cleanup() {
if (this.response.template === 'problem_main.html' && this.request.json) {
const {
path, page, pcount, ppcount, pdocs, psdict, category,
} = this.response.body;
this.response.body = {
title: this.renderTitle(category),
fragments: (await Promise.all([
this.renderHTML('partials/problem_list.html', {
page, ppcount, pcount, pdocs, psdict,
}),
this.renderHTML('partials/problem_stat.html', { pcount }),
this.renderHTML('partials/problem_lucky.html', { category }),
this.renderHTML('partials/path.html', { path }),
])).map((i) => ({ html: i })),
raw: {
path, page, pcount, ppcount, pdocs, psdict, category,
},
};
}
}
}
export class ProblemMainHandler extends ProblemHandler {
@param('page', Types.PositiveInt, true)
@param('q', Types.String, true)
async get(domainId: string, page = 1, q = '') {
this.response.template = 'problem_main.html';
const query: FilterQuery<Pdoc> = {};
5 years ago
let psdict = {};
const path: PathComponent[] = [
['Hydro', 'homepage'],
['problem_main', null],
];
if (q) {
query.$text = { $search: q };
path.push([q, null, null, true]);
}
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) query.hidden = false;
await bus.serial('problem/list', query, this);
const [pdocs, ppcount, pcount] = await paginate(
problem.getMulti(domainId, query).sort({ pid: 1, docId: 1 }),
5 years ago
page,
CONSTANT.PROBLEM_PER_PAGE,
5 years ago
);
if (this.user.hasPriv(PRIV.PRIV_USER_PROFILE)) {
psdict = await problem.getListStatus(
domainId, this.user._id, pdocs.map((pdoc) => pdoc.docId),
);
5 years ago
}
this.response.body = {
path, page, pcount, ppcount, pdocs, psdict, category: q,
5 years ago
};
}
@param('pid', Types.UnsignedInt)
async postStar(domainId: string, pid: number) {
await problem.setStar(domainId, pid, this.user._id, true);
this.back({ star: true });
}
@param('pid', Types.UnsignedInt)
async postUnstar(domainId: string, pid: number) {
await problem.setStar(domainId, pid, this.user._id, false);
this.back({ star: false });
5 years ago
}
}
export class ProblemCategoryHandler extends ProblemHandler {
@param('page', Types.PositiveInt, true)
@param('category', Types.String, null, parseCategory)
async get(domainId: string, page = 1, category: string[]) {
this.response.template = 'problem_main.html';
const q: any = { $and: [] };
for (const name of category) {
q.$and.push({
$or: [
{ category: { $elemMatch: { $eq: name } } },
{ tag: { $elemMatch: { $eq: name } } },
],
});
}
let psdict = {};
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
await bus.serial('problem/list', q, this);
const [pdocs, ppcount, pcount] = await paginate(
problem.getMulti(domainId, q).sort({ pid: 1, docId: 1 }),
page,
CONSTANT.PROBLEM_PER_PAGE,
);
if (this.user.hasPriv(PRIV.PRIV_USER_PROFILE)) {
psdict = await problem.getListStatus(
domainId, this.user._id, pdocs.map((pdoc) => pdoc.docId),
);
}
const path = [
4 years ago
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
[category, null, null, true],
];
this.response.body = {
path, page, pcount, ppcount, pdocs, psdict, category: category.join('+'),
};
}
5 years ago
}
export class ProblemRandomHandler extends ProblemHandler {
@param('category', Types.String, true, null, parseCategory)
async get(domainId: string, category: string[] = []) {
const q: any = category.length ? { $and: [] } : {};
for (const name of category) {
if (name) {
q.$and.push({
$or: [
{ category: { $elemMatch: { $eq: name } } },
{ tag: { $elemMatch: { $eq: name } } },
],
});
}
}
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
await bus.serial('problem/list', q, this);
const pid = await problem.random(domainId, q);
5 years ago
if (!pid) throw new NoProblemError();
this.response.body = { pid };
this.response.redirect = this.url('problem_detail', { pid });
5 years ago
}
}
export class ProblemDetailHandler extends ProblemHandler {
pdoc: Pdoc;
udoc: User;
@route('pid', Types.String, true, null, parsePid)
async _prepare(domainId: string, pid: number | string) {
this.response.template = 'problem_detail.html';
this.pdoc = await problem.get(domainId, pid, this.user._id);
if (!this.pdoc) throw new ProblemNotFoundError(domainId, pid);
if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
5 years ago
}
await bus.serial('problem/get', this.pdoc, this);
this.udoc = await user.getById(domainId, this.pdoc.owner);
this.response.body = {
5 years ago
pdoc: this.pdoc,
udoc: this.udoc,
title: this.pdoc.title,
path: [
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
[this.pdoc.title, null, true],
],
5 years ago
};
}
5 years ago
async get(...args: any[]) { } // eslint-disable-line
@param('pid', Types.UnsignedInt)
async postRejudge(domainId: string, pid: number) {
this.checkPerm(PERM.PERM_REJUDGE_PROBLEM);
// TODO maybe async?
await record.getMulti(domainId, { pid }).forEach(async (doc) => {
await record.reset(domainId, doc._id, true);
await record.judge(domainId, doc._id, -1);
});
this.back();
}
5 years ago
}
export class ProblemExportHandler extends ProblemDetailHandler {
async get() {
const hasPerm = (this.user._id === this.pdoc.owner && this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA_SELF))
|| this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA_SELF);
const pdoc = pick(this.pdoc, ['pid', 'acMsg', 'content', 'config', 'title', 'html', 'tag', 'category']);
const zip = new AdmZip();
if (hasPerm) {
const files = await storage.list(`problem/${this.domainId}/${this.pdoc.docId}/`, true);
for (const file of files) {
// eslint-disable-next-line no-await-in-loop
const buf = await streamToBuffer(await storage.get(`${file.prefix}${file.name}`));
zip.addFile(file.name, buf);
}
}
zip.addFile('problem.json', Buffer.from(JSON.stringify(pdoc)));
this.response.attachment(`${this.pdoc.title}.zip`, zip.toBuffer());
}
}
export class ProblemSubmitHandler extends ProblemDetailHandler {
@param('pid', Types.String, null, parsePid)
async get(domainId: string, pid: string | number) {
this.response.template = 'problem_submit.html';
const rdocs = await record
.getUserInProblemMulti(domainId, this.user._id, this.pdoc.docId)
5 years ago
.sort({ _id: -1 })
.limit(10)
.toArray();
this.response.body = {
5 years ago
path: [
4 years ago
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
[this.pdoc.title, 'problem_detail', { pid }, true],
5 years ago
['problem_submit', null],
5 years ago
],
pdoc: this.pdoc,
udoc: this.udoc,
rdocs,
5 years ago
title: this.pdoc.title,
5 years ago
};
}
5 years ago
@param('lang', Types.String)
@param('code', Types.String)
async post(domainId: string, lang: string, code: string) {
const rid = await record.add(domainId, this.pdoc.docId, this.user._id, lang, code, true);
const [rdoc] = await Promise.all([
record.get(domainId, rid),
problem.inc(domainId, this.pdoc.docId, 'nSubmit', 1),
domain.incUserInDomain(domainId, this.user._id, 'nSubmit'),
]);
bus.boardcast('record/change', rdoc);
this.response.body = { rid };
this.response.redirect = this.url('record_detail', { rid });
5 years ago
}
}
export class ProblemPretestHandler extends ProblemDetailHandler {
@param('lang', Types.String)
@param('code', Types.String)
@param('input', Types.String, true)
async post(domainId: string, lang: string, code: string, input: string = '') {
this.limitRate('add_record', 3600, 100);
// TODO parseConfig
const rid = await record.add(
domainId, this.pdoc.docId, this.user._id,
lang, code, true,
{
input,
time: '1s',
memory: '512m',
},
);
const rdoc = await record.get(domainId, rid);
bus.boardcast('record/change', rdoc);
this.response.body = { rid };
}
}
export class ProblemPretestConnectionHandler extends ConnectionHandler {
pid: string;
domainId: string;
dispose: bus.Disposable;
@param('pid', Types.String)
async prepare(domainId: string, pid: string) {
const pdoc = await problem.get(domainId, pid);
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
if (pdoc.hidden) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
this.pid = pdoc.docId.toString();
this.domainId = domainId;
this.dispose = bus.on('record/change', this.onRecordChange.bind(this));
}
async onRecordChange(rdoc: Rdoc) {
if (
rdoc.uid !== this.user._id
|| rdoc.pid.toString() !== this.pid
|| rdoc.domainId !== this.domainId
) return;
rdoc.compilerTexts = [];
rdoc.judgeTexts = [];
// @ts-ignore
rdoc.testCases = rdoc.testCases.map((c) => ({
status: c.status,
}));
// TODO handle update
if (rdoc.contest) return;
this.send({ rdoc });
}
async cleanup() {
if (this.dispose) this.dispose();
}
}
export class ProblemStatisticsHandler extends ProblemDetailHandler {
async get(domainId: string) {
const udoc = await user.getById(domainId, this.pdoc.owner);
const path = [
4 years ago
['problem_main', 'problem_main'],
[this.pdoc.title, 'problem_detail', { pid: this.pdoc.pid }, true],
['problem_statistics', null],
];
this.response.template = 'problem_statistics.html';
this.response.body = { pdoc: this.pdoc, udoc, path };
}
}
export class ProblemManageHandler extends ProblemDetailHandler {
5 years ago
async prepare() {
if (this.pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SELF);
5 years ago
}
}
export class ProblemSettingsHandler extends ProblemManageHandler {
@param('pid', Types.String)
async get(domainId: string, pid: string) {
this.response.template = 'problem_settings.html';
this.response.body.path = [
4 years ago
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
[this.pdoc.title, 'problem_detail', { pid }, true],
5 years ago
['problem_settings', null],
5 years ago
];
if (this.response.body.pdoc.config) {
this.response.body.pdoc.config = yaml.safeDump(this.response.body.pdoc.config);
}
5 years ago
}
5 years ago
@param('pid', Types.String, null, parsePid)
@param('yaml', Types.String)
async postConfig(domainId: string, pid: string | number, cfg: string) {
const pdoc = await problem.get(domainId, pid);
// TODO validate
4 years ago
const config = yaml.safeLoad(cfg) as any;
await problem.edit(domainId, pdoc.docId, { config });
4 years ago
this.back();
}
@param('pid', Types.String, null, parsePid)
@param('hidden', Types.Boolean)
@param('category', Types.String, true, null, parseCategory)
@param('tag', Types.String, true, null, parseCategory)
@param('difficultySetting', Types.UnsignedInt)
@param('difficultyAdmin', Types.UnsignedInt, true)
async postSetting(
domainId: string, pid: string | number, hidden = false,
category: string[] = [], tag: string[] = [],
difficultySetting: string, difficultyAdmin: number,
) {
const pdoc = await problem.get(domainId, pid);
if (!problem.SETTING_DIFFICULTY_RANGE[difficultySetting]) {
throw new ValidationError('difficultySetting');
}
if (!difficultyAdmin) difficultyAdmin = null;
else if (difficultyAdmin < 1 || difficultyAdmin > 9) throw new ValidationError('difficultyAdmin');
const update: Partial<Pdoc> = {
hidden, category, tag, difficultySetting, difficultyAdmin,
};
await bus.serial('problem/setting', update, this);
await problem.edit(domainId, pdoc.docId, update);
await global.Hydro.script.difficulty.run({ domainId, pid }, console.log);
this.back();
5 years ago
}
}
export class ProblemEditHandler extends ProblemManageHandler {
async get({ pid }) {
this.response.template = 'problem_edit.html';
this.response.body.path = [
4 years ago
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
[this.pdoc.title, 'problem_detail', { pid }, true],
5 years ago
['problem_edit', null],
5 years ago
];
}
5 years ago
@param('title', Types.String, isTitle)
@param('content', Types.String, isContent)
@post('pid', Types.String, isPid, true)
async post(domainId: string, title: string, content: string, newPid: string = '') {
try {
content = JSON.parse(content);
} catch { /* Ignore */ }
const $update: Partial<Pdoc> = { title, content, pid: newPid };
let pdoc = await problem.get(domainId, this.request.params.pid);
pdoc = await problem.edit(domainId, pdoc.docId, $update);
this.response.redirect = this.url('problem_detail', { pid: pdoc.pid || pdoc.docId });
5 years ago
}
}
export class ProblemFilesHandler extends ProblemDetailHandler {
@param('testdata', Types.Boolean)
@param('additional_file', Types.Boolean)
async get(domainId: string, getTestdata = true, getAdditionalFile = true) {
4 years ago
const canReadData = this.user._id === this.pdoc.owner || this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
const [testdata, additional_file] = await Promise.all([
4 years ago
getTestdata && canReadData
? storage.list(`problem/${this.pdoc.domainId}/${this.pdoc.docId}/testdata/`, true)
: [],
getAdditionalFile
4 years ago
? storage.list(`problem/${this.pdoc.domainId}/${this.pdoc.docId}/additional_file/`, true)
: [],
]);
this.response.body.testdata = testdata;
this.response.body.additional_file = additional_file;
this.response.template = 'problem_files.html';
5 years ago
}
@post('files', Types.Array)
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postGetLinks(domainId: string, files: string[], type = 'testdata') {
const isJudge = this.user.hasPriv(PRIV.PRIV_JUDGE);
if (type === 'testdata' && !isJudge) {
if (this.user._id !== this.pdoc.owner) {
this.checkPerm(PERM.PERM_READ_PROBLEM_DATA);
} else this.checkPerm(PERM.PERM_READ_PROBLEM_DATA_SELF);
4 years ago
}
const links = {};
for (const file of files) {
// eslint-disable-next-line no-await-in-loop
links[file] = await storage.signDownloadLink(
`problem/${this.pdoc.domainId}/${this.pdoc.docId}/${type}/${file}`,
file, false, isJudge ? 'judge' : 'user',
);
}
this.response.body.links = links;
}
@post('filename', Types.String)
@post('type', Types.String, true)
async postUploadFile(domainId: string, filename: string, type = 'testdata') {
if (!this.request.files.file) throw new ValidationError('file');
if (this.pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SELF);
if (filename === 'testdata.zip') {
const zip = new AdmZip(this.request.files.file.path);
const entries = zip.getEntries();
for (const entry of entries) {
if (type === 'testdata') {
// eslint-disable-next-line no-await-in-loop
await problem.addTestdata(domainId, this.pdoc.docId, entry.name, entry.getData());
} else {
// eslint-disable-next-line no-await-in-loop
await storage.put(`problem/${this.pdoc.domainId}/${this.pdoc.docId}/additional_file/${entry.name}`, entry.getData());
}
4 years ago
}
} else if (type === 'testdata') {
await problem.addTestdata(domainId, this.pdoc.docId, filename, this.request.files.file.path);
} else {
await storage.put(`problem/${this.pdoc.domainId}/${this.pdoc.docId}/additional_file/${filename}`, this.request.files.file.path);
}
this.back();
}
@post('files', Types.Array)
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postDeleteFiles(domainId: string, files: string[], type = 'testdata') {
if (this.pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SELF);
if (type === 'testdata') {
await problem.delTestdata(domainId, this.pdoc.docId, files);
} else {
await storage.del(files.map((file) => `problem/${this.pdoc.domainId}/${this.pdoc.docId}/${type}/${file}`));
}
this.back();
5 years ago
}
}
export class ProblemSolutionHandler extends ProblemDetailHandler {
@param('page', Types.PositiveInt, true)
async get(domainId: string, page = 1) {
this.response.template = 'problem_solution.html';
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
5 years ago
const [psdocs, pcount, pscount] = await paginate(
solution.getMulti(domainId, this.pdoc.docId),
5 years ago
page,
CONSTANT.SOLUTION_PER_PAGE,
5 years ago
);
const uids = [this.pdoc.owner];
const docids = [];
5 years ago
for (const psdoc of psdocs) {
docids.push(psdoc.docId);
5 years ago
uids.push(psdoc.owner);
if (psdoc.reply.length) {
for (const psrdoc of psdoc.reply) uids.push(psrdoc.owner);
}
5 years ago
}
const udict = await user.getList(domainId, uids);
4 years ago
const pssdict = solution.getListStatus(domainId, docids, this.user._id);
5 years ago
const path = [
4 years ago
['problem_main', 'problem_main'],
[this.pdoc.title, 'problem_detail', { pid: this.pdoc.pid }, true],
5 years ago
['problem_solution', null],
5 years ago
];
this.response.body = {
5 years ago
path, psdocs, page, pcount, pscount, udict, pssdict, pdoc: this.pdoc,
};
5 years ago
}
5 years ago
@param('content', Types.String, isContent)
async postSubmit(domainId: string, content: string) {
this.checkPerm(PERM.PERM_CREATE_PROBLEM_SOLUTION);
4 years ago
await solution.add(domainId, this.pdoc.docId, this.user._id, content);
this.back();
5 years ago
}
5 years ago
@param('content', Types.String, isContent)
@param('psid', Types.ObjectID)
async postEditSolution(domainId: string, content: string, psid: ObjectID) {
let psdoc = await solution.get(domainId, psid);
if (psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION);
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF);
psdoc = await solution.edit(domainId, psdoc.docId, content);
this.back({ psdoc });
5 years ago
}
5 years ago
@param('psid', Types.ObjectID)
async postDeleteSolution(domainId: string, psid: ObjectID) {
const psdoc = await solution.get(domainId, psid);
if (psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION);
else this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF);
await solution.del(domainId, psdoc.docId);
this.back();
5 years ago
}
5 years ago
@param('psid', Types.ObjectID)
@param('content', Types.String, isContent)
async postReply(domainId: string, psid: ObjectID, content: string) {
this.checkPerm(PERM.PERM_REPLY_PROBLEM_SOLUTION);
const psdoc = await solution.get(domainId, psid);
4 years ago
await solution.reply(domainId, psdoc.docId, this.user._id, content);
this.back();
5 years ago
}
5 years ago
@param('psid', Types.ObjectID)
@param('psrid', Types.ObjectID)
@param('content', Types.String, isContent)
async postEditReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) {
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
if (!(psrdoc.owner === this.user._id
&& this.user.hasPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF))) {
throw new PermissionError(PERM.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF);
}
await solution.editReply(domainId, psid, psrid, content);
this.back();
5 years ago
}
5 years ago
@param('psid', Types.ObjectID)
@param('psrid', Types.ObjectID)
async postDeleteReply(domainId: string, psid: ObjectID, psrid: ObjectID) {
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
if (!(psrdoc.owner === this.user._id
&& this.user.hasPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF))) {
this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY);
}
await solution.delReply(domainId, psid, psrid);
this.back();
5 years ago
}
5 years ago
@param('psid', Types.ObjectID)
async postUpvote(domainId: string, psid: ObjectID) {
const [psdoc, pssdoc] = await solution.vote(domainId, psid, this.user._id, 1);
this.back({ vote: psdoc.vote, user_vote: pssdoc.vote });
}
5 years ago
@param('psid', Types.ObjectID)
async postDownvote(domainId: string, psid: ObjectID) {
const [psdoc, pssdoc] = await solution.vote(domainId, psid, this.user._id, -1);
this.back({ vote: psdoc.vote, user_vote: pssdoc.vote });
}
5 years ago
}
export class ProblemSolutionRawHandler extends ProblemDetailHandler {
@param('psid', Types.ObjectID)
@route('psrid', Types.ObjectID, true)
async get(domainId: string, psid: ObjectID, psrid?: ObjectID) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
if (psrid) {
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid, psrid);
this.response.body = psrdoc.content;
} else {
const psdoc = await solution.get(domainId, psid);
this.response.body = psdoc.content;
}
this.response.type = 'text/markdown';
5 years ago
}
}
export class ProblemCreateHandler extends Handler {
5 years ago
async get() {
this.response.template = 'problem_edit.html';
this.response.body = {
5 years ago
path: [
4 years ago
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
5 years ago
['problem_create', null],
],
page_name: 'problem_create',
5 years ago
};
}
5 years ago
@param('title', Types.String, isTitle)
@param('pid', Types.String, isPid, true)
@param('content', Types.String, isContent)
@param('hidden', Types.Boolean)
async post(domainId: string, title: string, pid: string, content: string, hidden = false) {
try {
content = JSON.parse(content);
} catch { /* Ignore */ }
4 years ago
const docId = await problem.add(
domainId, pid, title, content,
this.user._id, [], [], hidden,
4 years ago
);
this.response.body = { pid: docId };
this.response.redirect = this.url('problem_settings', { pid: docId });
5 years ago
}
}
export class ProblemImportHandler extends Handler {
async get() {
this.response.body = { type: 'Hydro' };
this.response.template = 'problem_import.html';
}
4 years ago
async post({ domainId }) {
if (!this.request.files.file) throw new ValidationError('file');
const stat = await fs.stat(this.request.files.file.path);
if (stat.size > 128 * 1024 * 1024) throw new BadRequestError('File too large');
const zip = new AdmZip(this.request.files.file.path);
const pdoc = JSON.parse(zip.getEntry('problem.json').getData().toString());
const pid = await problem.add(domainId, pdoc.pid, pdoc.title, pdoc.content, this.user._id, pdoc.tags, pdoc.category);
const entries = zip.getEntries();
for (const entry of entries) {
if (entry.name.startsWith('testdata/') || entry.name.startsWith('additional_file')) {
// eslint-disable-next-line no-await-in-loop
await storage.put(`problem/${domainId}/${pid}/${entry.name}`, entry.getData());
}
}
const data = await storage.list(`problem/${domainId}/${pid}/testdata/`, true);
await problem.edit(domainId, pid, { html: pdoc.html, data });
this.response.redirect = this.url('problem_detail', { pid });
}
}
export async function apply() {
ProblemAdd('problem_import', {}, 'copy', 'Import From Hydro');
Route('problem_main', '/p', ProblemMainHandler);
Route('problem_category', '/p/category/:category', ProblemCategoryHandler);
Route('problem_random', '/problem/random', ProblemRandomHandler);
Route('problem_detail', '/p/:pid', ProblemDetailHandler);
Route('problem_export', '/p/:pid/export', ProblemExportHandler);
Route('problem_submit', '/p/:pid/submit', ProblemSubmitHandler, PERM.PERM_SUBMIT_PROBLEM);
Route('problem_pretest', '/p/:pid/pretest', ProblemPretestHandler);
Route('problem_settings', '/p/:pid/settings', ProblemSettingsHandler);
Route('problem_statistics', '/p/:pid/statistics', ProblemStatisticsHandler);
Route('problem_edit', '/p/:pid/edit', ProblemEditHandler);
Route('problem_files', '/p/:pid/files', ProblemFilesHandler);
Route('problem_solution', '/p/:pid/solution', ProblemSolutionHandler);
Route('problem_solution_raw', '/p/:pid/solution/:psid/raw', ProblemSolutionRawHandler);
Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler);
Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
Route('problem_import', '/problem/import', ProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
Connection('problem_pretest_conn', '/conn/pretest', ProblemPretestConnectionHandler);
5 years ago
}
global.Hydro.handler.problem = apply;