mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
从syzoj系统导入题目
This commit is contained in:
1 parent
97b0d782b9
commit
ce3d11b999
12 files changed
+179
-34
No files matched your search
@@ -35,6 +35,7 @@ async function run() {
|
|||||||
require('./handler/user');
|
require('./handler/user');
|
||||||
require('./handler/contest');
|
require('./handler/contest');
|
||||||
require('./handler/training');
|
require('./handler/training');
|
||||||
|
require('./handler/import');
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
process.on('restart', async () => {
|
process.on('restart', async () => {
|
||||||
|
|||||||
+8
-2
@@ -32,14 +32,18 @@ class NotFoundError extends UserFacingError {
|
|||||||
this.code = 404;
|
this.code = 404;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class RemoteOnlineJudgeError extends UserFacingError {
|
||||||
|
constructor(message) {
|
||||||
|
super('RemoteOnlineJudgeError');
|
||||||
|
this.params = [message];
|
||||||
|
}
|
||||||
|
}
|
||||||
class AlreadyVotedError extends BadRequestError {
|
class AlreadyVotedError extends BadRequestError {
|
||||||
constructor(psid, uid) {
|
constructor(psid, uid) {
|
||||||
super('You\'ve already voted.');
|
super('You\'ve already voted.');
|
||||||
this.params = [psid, uid];
|
this.params = [psid, uid];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginError extends ForbiddenError {
|
class LoginError extends ForbiddenError {
|
||||||
constructor(uname) {
|
constructor(uname) {
|
||||||
super('LoginError');
|
super('LoginError');
|
||||||
@@ -160,6 +164,7 @@ class ProblemDataNotFoundError extends NotFoundError {
|
|||||||
this.params = [pid];
|
this.params = [pid];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
ForbiddenError,
|
ForbiddenError,
|
||||||
@@ -187,6 +192,7 @@ module.exports = {
|
|||||||
UserFacingError,
|
UserFacingError,
|
||||||
SystemError,
|
SystemError,
|
||||||
TrainingAlreadyEnrollError,
|
TrainingAlreadyEnrollError,
|
||||||
|
RemoteOnlineJudgeError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const download = require('../lib/download');
|
||||||
|
const problem = require('../model/problem');
|
||||||
|
const { Route, Handler } = require('../service/server');
|
||||||
|
const { PERM_CREATE_PROBLEM } = require('../permission');
|
||||||
|
const axios = require('../lib/axios');
|
||||||
|
const { ValidationError, RemoteOnlineJudgeError } = require('../error');
|
||||||
|
|
||||||
|
class ProblemImportHandler extends Handler {
|
||||||
|
async syzoj(url) {
|
||||||
|
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9\.]+)\/problem\/([0-9]+)\/?/i;
|
||||||
|
assert(url.match(RE_SYZOJ), new ValidationError('url'));
|
||||||
|
if (!url.endsWith('/')) url += '/';
|
||||||
|
const [, host, pid] = RE_SYZOJ.exec(url);
|
||||||
|
const res = await axios.get(`${url}export`);
|
||||||
|
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
|
||||||
|
assert(res.data.success, new RemoteOnlineJudgeError((res.data.error || {}).message));
|
||||||
|
const p = res.data.obj;
|
||||||
|
const content = [
|
||||||
|
this.translate('problem.import.problem_description'),
|
||||||
|
p.description,
|
||||||
|
this.translate('problem.import.input_format'),
|
||||||
|
p.input_format,
|
||||||
|
this.translate('problem.import.output_format'),
|
||||||
|
p.output_format,
|
||||||
|
this.translate('problem.import.hint'),
|
||||||
|
p.hint,
|
||||||
|
this.translate('problem.import.limit_and_hint'),
|
||||||
|
p.limit_and_hint,
|
||||||
|
];
|
||||||
|
if (p.have_additional_file) {
|
||||||
|
content.push(
|
||||||
|
this.translate('problem.import.additional_file'),
|
||||||
|
`${url}download/additional_file`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const pdoc = {
|
||||||
|
title: p.title,
|
||||||
|
content: content.join(' \n'),
|
||||||
|
owner: this.user._id,
|
||||||
|
from: url,
|
||||||
|
pid: `${host}_${pid}`,
|
||||||
|
config: {
|
||||||
|
time: p.time_limit,
|
||||||
|
memory: p.memory_limit * 1024,
|
||||||
|
filename: p.file_io_input_name,
|
||||||
|
type: p.type,
|
||||||
|
tags: p.tags,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const r = await download(`${url}testdata/download`);
|
||||||
|
return [pdoc, r];
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare() {
|
||||||
|
this.checkPerm(PERM_CREATE_PROBLEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
this.response.template = 'problem_import.html';
|
||||||
|
this.response.body = {
|
||||||
|
path: [
|
||||||
|
['Hydro', '/'],
|
||||||
|
['problem_main', '/p'],
|
||||||
|
['problem_import', null],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async post({
|
||||||
|
url, pid, hidden, remoteType,
|
||||||
|
}) {
|
||||||
|
if (remoteType !== 'syzoj') throw new ValidationError('remoteType');
|
||||||
|
const [pdoc, testdata] = await this.syzoj(url);
|
||||||
|
if (pid) pdoc.pid = pid;
|
||||||
|
if (hidden) pdoc.hidden = true;
|
||||||
|
const _id = await problem.add(pdoc);
|
||||||
|
console.log(_id);
|
||||||
|
await problem.setTestdata(_id, testdata);
|
||||||
|
this.response.body = { pid: pid || _id };
|
||||||
|
this.response.redirect = `/p/${pid || _id}/settings`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Route('/problem/import', ProblemImportHandler);
|
||||||
@@ -13,8 +13,8 @@ async function next(body) {
|
|||||||
let rdoc = await record.get(body.rid);
|
let rdoc = await record.get(body.rid);
|
||||||
const $set = {};
|
const $set = {};
|
||||||
if (body.case) {
|
if (body.case) {
|
||||||
rdoc.cases.push(body.case);
|
rdoc.testCases.push(body.case);
|
||||||
$set.cases = rdoc.cases;
|
$set.testCases = rdoc.testCases;
|
||||||
}
|
}
|
||||||
if (body.judge_text) {
|
if (body.judge_text) {
|
||||||
rdoc.judgeTexts.push(body.judge_text);
|
rdoc.judgeTexts.push(body.judge_text);
|
||||||
@@ -35,8 +35,8 @@ async function end(body) {
|
|||||||
let rdoc = await record.get(body.rid);
|
let rdoc = await record.get(body.rid);
|
||||||
const $set = {};
|
const $set = {};
|
||||||
if (body.case) {
|
if (body.case) {
|
||||||
rdoc.cases.push(body.case);
|
rdoc.testCases.push(body.case);
|
||||||
$set.cases = rdoc.cases;
|
$set.testCases = rdoc.testCases;
|
||||||
}
|
}
|
||||||
if (body.judge_text) {
|
if (body.judge_text) {
|
||||||
rdoc.judgeTexts.push(body.judge_text);
|
rdoc.judgeTexts.push(body.judge_text);
|
||||||
|
|||||||
@@ -142,12 +142,12 @@ class ProblemSettingsHandler extends ProblemManageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ProblemEditHandler extends ProblemManageHandler {
|
class ProblemEditHandler extends ProblemManageHandler {
|
||||||
async get() {
|
async get({ pid }) {
|
||||||
this.response.template = 'problem_edit.html';
|
this.response.template = 'problem_edit.html';
|
||||||
this.response.body.path = [
|
this.response.body.path = [
|
||||||
['Hydro', '/'],
|
['Hydro', '/'],
|
||||||
['problem_main', '/p'],
|
['problem_main', '/p'],
|
||||||
[this.pdoc.title, `/p/${this.request.params.pid}`, true],
|
[this.pdoc.title, `/p/${pid}`, true],
|
||||||
['problem_edit', null],
|
['problem_edit', null],
|
||||||
];
|
];
|
||||||
this.response.body.page_name = 'problem_edit';
|
this.response.body.page_name = 'problem_edit';
|
||||||
@@ -177,14 +177,7 @@ class ProblemDataUploadHandler extends ProblemManageHandler {
|
|||||||
async post() {
|
async post() {
|
||||||
if (!this.request.files.file) throw new BadRequestError();
|
if (!this.request.files.file) throw new BadRequestError();
|
||||||
const r = fs.createReadStream(this.request.files.file.path);
|
const r = fs.createReadStream(this.request.files.file.path);
|
||||||
const f = gridfs.openUploadStream('data.zip');
|
await problem.setTestdata(this.pdoc._id, r);
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
r.pipe(f);
|
|
||||||
f.once('finish', resolve);
|
|
||||||
f.once('error', reject);
|
|
||||||
});
|
|
||||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') { gridfs.delete(this.pdoc.data); }
|
|
||||||
this.pdoc = await problem.edit(this.pdoc._id, { data: f.id });
|
|
||||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') {
|
if (this.pdoc.data && typeof this.pdoc.data === 'object') {
|
||||||
const files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
const files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||||
this.md5 = files[0].md5;
|
this.md5 = files[0].md5;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function get(url, options) {
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await axios.get(url, options);
|
||||||
|
} catch (e) {
|
||||||
|
res = e;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function post(url, data, options) {
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await axios.post(url, data, options);
|
||||||
|
} catch (e) {
|
||||||
|
res = e;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { get, post };
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
async function download(url, path, retry = 3) {
|
||||||
|
let r;
|
||||||
|
try {
|
||||||
|
const res = await axios.get(url, { responseType: 'stream' });
|
||||||
|
if (path) {
|
||||||
|
const w = await fs.createWriteStream(path);
|
||||||
|
res.data.pipe(w);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
w.on('finish', resolve);
|
||||||
|
w.on('error', reject);
|
||||||
|
});
|
||||||
|
} else r = res.data;
|
||||||
|
} catch (e) {
|
||||||
|
if (retry) r = await download(url, path, retry - 1);
|
||||||
|
else throw e;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
module.exports = download;
|
||||||
+15
-2
@@ -1,4 +1,5 @@
|
|||||||
const { ObjectID } = require('bson');
|
const { ObjectID } = require('bson');
|
||||||
|
const gridfs = require('../service/gridfs');
|
||||||
const { ProblemNotFoundError } = require('../error');
|
const { ProblemNotFoundError } = require('../error');
|
||||||
const validator = require('../lib/validator');
|
const validator = require('../lib/validator');
|
||||||
const db = require('../service/db.js');
|
const db = require('../service/db.js');
|
||||||
@@ -34,7 +35,7 @@ async function add({
|
|||||||
}) {
|
}) {
|
||||||
validator.checkTitle(title);
|
validator.checkTitle(title);
|
||||||
validator.checkContent(content);
|
validator.checkContent(content);
|
||||||
await coll.insertOne({
|
const res = await coll.insertOne({
|
||||||
content,
|
content,
|
||||||
owner,
|
owner,
|
||||||
pid,
|
pid,
|
||||||
@@ -46,7 +47,7 @@ async function add({
|
|||||||
nSubmit: 0,
|
nSubmit: 0,
|
||||||
nAccept: 0,
|
nAccept: 0,
|
||||||
});
|
});
|
||||||
return pid;
|
return res.insertedId;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {string|ObjectID} pid
|
* @param {string|ObjectID} pid
|
||||||
@@ -133,6 +134,17 @@ async function getListStatus(uid, pids) {
|
|||||||
for (const psdoc of psdocs) r[psdoc.pid] = psdoc;
|
for (const psdoc of psdocs) r[psdoc.pid] = psdoc;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
async function setTestdata(_id, readStream) {
|
||||||
|
const pdoc = await getById(_id);
|
||||||
|
const f = gridfs.openUploadStream('data.zip');
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
readStream.pipe(f);
|
||||||
|
f.once('finish', resolve);
|
||||||
|
f.once('error', reject);
|
||||||
|
});
|
||||||
|
if (pdoc.data && typeof pdoc.data === 'object') gridfs.delete(this.pdoc.data);
|
||||||
|
return await edit(_id, { data: f.id });
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
add,
|
add,
|
||||||
@@ -146,4 +158,5 @@ module.exports = {
|
|||||||
getList,
|
getList,
|
||||||
getListStatus,
|
getListStatus,
|
||||||
getMultiStatus,
|
getMultiStatus,
|
||||||
|
setTestdata,
|
||||||
};
|
};
|
||||||
@@ -52,7 +52,7 @@ function reset(rid) {
|
|||||||
status: STATUS_WAITING,
|
status: STATUS_WAITING,
|
||||||
time: 0,
|
time: 0,
|
||||||
memory: 0,
|
memory: 0,
|
||||||
cases: [],
|
testCases: [],
|
||||||
judgeTexts: [],
|
judgeTexts: [],
|
||||||
compilerTexts: [],
|
compilerTexts: [],
|
||||||
judgeAt: null,
|
judgeAt: null,
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ class Handler {
|
|||||||
this.response.redirect = this.request.headers.referer || '/';
|
this.response.redirect = this.request.headers.referer || '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
translate(str) {
|
||||||
|
return str ? str.toString().translate(this.user.language) : '';
|
||||||
|
}
|
||||||
|
|
||||||
binary(data, name) {
|
binary(data, name) {
|
||||||
this.response.body = data;
|
this.response.body = data;
|
||||||
this.response.template = null;
|
this.response.template = null;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{% set page_name = "problem_import" %}
|
||||||
{% extends "layout/basic.html" %}
|
{% extends "layout/basic.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -6,19 +7,22 @@
|
|||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="medium-8 columns">
|
<div class="medium-2 columns">
|
||||||
|
<label>
|
||||||
|
{{ _('Source type') }}
|
||||||
|
<input name="remoteType" placeholder="syzoj" class="textbox" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="medium-6 columns">
|
||||||
<label>
|
<label>
|
||||||
{{ _('Import from') }}
|
{{ _('Import from') }}
|
||||||
<input name="src" placeholder="{{ _('Source') }}" class="textbox" autocomplete="off">
|
<input name="url" placeholder="{{ _('Source') }}" class="textbox" autocomplete="off">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-2 columns">
|
<div class="medium-2 columns">
|
||||||
<label>
|
<label>
|
||||||
{{ _('Settings') }}
|
{{ _('Settings') }}
|
||||||
<br>
|
<input name="pid" placeholder="{{ _('ProblemID') }}" class="textbox" autocomplete="off">
|
||||||
<label class="checkbox">
|
|
||||||
<input name="src" placeholder="{{ _('ProblemID') }}" class="textbox" autocomplete="off">
|
|
||||||
</label>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-2 columns">
|
<div class="medium-2 columns">
|
||||||
@@ -30,16 +34,10 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row"><div class="columns">
|
|
||||||
<label>
|
|
||||||
{{ _('Source URL') }}
|
|
||||||
<textarea name="src" class="textbox" style="height: 300px"></textarea>
|
|
||||||
</label>
|
|
||||||
</div></div>
|
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<button type="submit" class="rounded primary button">
|
<button type="submit" class="rounded primary button">
|
||||||
{{ _('Copy') }}
|
{{ _('Import') }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="rounded button" onclick="window.history.go(-1)">
|
<button type="button" class="rounded button" onclick="window.history.go(-1)">
|
||||||
{{ _('Cancel') }}
|
{{ _('Cancel') }}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<pre class="judge-text">{{ rdoc.judgeTexts|join('\n') }}</pre>
|
<pre class="judge-text">{{ rdoc.judgeTexts|join('\n') }}</pre>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if rdoc.cases.length %}
|
{% if rdoc.testCases.length %}
|
||||||
<div class="section__body no-padding">
|
<div class="section__body no-padding">
|
||||||
<table class="data-table record_detail__table">
|
<table class="data-table record_detail__table">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for rcdoc in rdoc.cases %}
|
{% for rcdoc in rdoc.testCases %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="col--case record-status--border {{ model.builtin.STATUS_CODES[rcdoc['status']] }}">
|
<td class="col--case record-status--border {{ model.builtin.STATUS_CODES[rcdoc['status']] }}">
|
||||||
#{{ loop.index }}
|
#{{ loop.index }}
|
||||||
|
|||||||
Reference in new issue
Block a user