Browse Source

Add import problem

master
Menci 8 years ago
parent
commit
ba80d1201e
  1. 5
      config-example.json
  2. 54
      models/problem.js
  3. 138
      modules/problem.js
  4. 5
      package.json
  5. 16
      views/problem_import.ejs
  6. 21
      views/problems.ejs

5
config-example.json

@ -20,6 +20,11 @@
"show": true "show": true
} }
}, },
"limit": {
"time_limit": 10000,
"memory_limit": 1024,
"data_size": 209715200
},
"page": { "page": {
"problem": 50, "problem": 50,
"problem_statistics": 10, "problem_statistics": 10,

54
models/problem.js

@ -242,11 +242,14 @@ class Problem extends Model {
} }
async updateTestdata(path) { async updateTestdata(path) {
let fs = Promise.promisifyAll(require('fs')); let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(path); let buf = await fs.readFileAsync(path);
if (buf.length > syzoj.config.limit.data_size) throw 'Testdata too large.'
let key = syzoj.utils.md5(buf); let key = syzoj.utils.md5(buf);
await fs.rename(path, TestData.resolvePath(key)); await fs.moveAsync(path, TestData.resolvePath(key), { overwrite: true });
if (this.testdata_id) { if (this.testdata_id) {
let tmp = this.testdata_id; let tmp = this.testdata_id;
@ -262,6 +265,26 @@ class Problem extends Model {
}); });
await file.save(); await file.save();
this.testdata_id = file.id; this.testdata_id = file.id;
await this.save();
}
async validate() {
if (this.time_limit <= 0) return 'Invalid time limit';
if (this.time_limit > syzoj.config.limit.time_limit) return 'Time limit too large';
if (this.memory_limit <= 0) return 'Invalid memory limit';
if (this.memory_limit > syzoj.config.limit.memory_limit) return 'Memory limit too large';
let filenameRE = /^[\w \-\+\.]*$/;
if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name';
if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name';
if (this.file_io) {
if (!this.file_io_input_name) return 'No input file name';
if (!this.file_io_output_name) return 'No output file name';
}
return null;
} }
async getJudgeState(user, acFirst) { async getJudgeState(user, acFirst) {
@ -371,6 +394,33 @@ class Problem extends Model {
return res; return res;
} }
async setTags(newTagIDs) {
let ProblemTagMap = syzoj.model('problem_tag_map');
let oldTagIDs = (await this.getTags()).map(x => x.id);
let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x));
let addTagIDs = newTagIDs.filter(x => !oldTagIDs.includes(x));
for (let tagID of delTagIDs) {
let map = await ProblemTagMap.findOne({ where: {
problem_id: this.id,
tag_id: tagID
} });
await map.destroy();
}
for (let tagID of addTagIDs) {
let map = await ProblemTagMap.create({
problem_id: this.id,
tag_id: tagID
});
await map.save();
}
}
getModel() { return model; } getModel() { return model; }
} }

138
modules/problem.js

@ -126,6 +126,38 @@ app.get('/problem/:id', async (req, res) => {
} }
}); });
app.get('/problem/:id/export', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem || !problem.is_public) throw 'No such problem.';
let obj = {
title: problem.title,
description: problem.description,
input_format: problem.input_format,
output_format: problem.output_format,
example: problem.example,
limit_and_hint: problem.limit_and_hint,
time_limit: problem.time_limit,
memory_limit: problem.memory_limit,
file_io: problem.file_io,
file_io_input_name: problem.file_io_input_name,
file_io_output_name: problem.file_io_output_name,
tags: []
};
let tags = await problem.getTags();
obj.tags = tags.map(tag => tag.name);
res.send({ success: true, obj: obj });
} catch (e) {
syzoj.log(e);
res.send({ success: false, error: e });
}
});
app.get('/problem/:id/edit', async (req, res) => { app.get('/problem/:id/edit', async (req, res) => {
try { try {
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
@ -156,7 +188,7 @@ app.get('/problem/:id/edit', async (req, res) => {
app.post('/problem/:id/edit', async (req, res) => { app.post('/problem/:id/edit', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id); let problem = await Problem.fromID(id);
if (!problem) { if (!problem) {
problem = await Problem.create(); problem = await Problem.create();
@ -183,28 +215,97 @@ app.post('/problem/:id/edit', async (req, res) => {
req.body.tags = [req.body.tags]; req.body.tags = [req.body.tags];
} }
let oldTagIDs = (await problem.getTags()).map(x => x.id);
let newTagIDs = await req.body.tags.map(x => parseInt(x)).filterAsync(async x => ProblemTag.fromID(x)); let newTagIDs = await req.body.tags.map(x => parseInt(x)).filterAsync(async x => ProblemTag.fromID(x));
await problem.setTags(newTagIDs);
let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x)); res.redirect(syzoj.utils.makeUrl(['problem', problem.id]));
let addTagIDs = newTagIDs.filter(x => !oldTagIDs.includes(x)); } catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
for (let tagID of delTagIDs) { app.get('/problem/:id/import', async (req, res) => {
let map = await ProblemTagMap.findOne({ where: { try {
problem_id: id, let id = parseInt(req.params.id) || 0;
tag_id: tagID let problem = await Problem.fromID(id);
} }); if (!problem) {
problem = await Problem.create();
problem.id = id;
problem.user_id = res.locals.user.id;
} else {
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied.';
if (!await problem.isAllowedEditBy(res.locals.user)) throw 'Permission denied.';
}
await map.destroy(); res.render('problem_import', {
problem: problem
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
} }
});
for (let tagID of addTagIDs) { app.post('/problem/:id/import', async (req, res) => {
let map = await ProblemTagMap.create({ try {
problem_id: id, let id = parseInt(req.params.id) || 0;
tag_id: tagID let problem = await Problem.fromID(id);
if (!problem) {
problem = await Problem.create();
if (id) problem.id = id;
problem.user_id = res.locals.user.id;
} else {
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied.';
if (!await problem.isAllowedEditBy(res.locals.user)) throw 'Permission denied.';
}
let request = require('request-promise');
let url = require('url');
let json = await request({
uri: url.resolve(req.body.url, 'export'),
timeout: 1500,
json: true
}); });
await map.save(); if (!json.success) throw `Failed to load problem: ${json.error}`;
problem.title = json.obj.title;
problem.description = json.obj.description;
problem.input_format = json.obj.input_format;
problem.output_format = json.obj.output_format;
problem.example = json.obj.example;
problem.limit_and_hint = json.obj.limit_and_hint;
problem.time_limit = json.obj.time_limit;
problem.memory_limit = json.obj.memory_limit;
problem.file_io = json.obj.file_io;
problem.file_io_input_name = json.obj.file_io_input_name;
problem.file_io_output_name = json.obj.file_io_output_name;
let validateMsg = await problem.validate();
if (validateMsg) throw 'Invalid problem: ' + validateMsg;
await problem.save();
let tagIDs = (await json.obj.tags.mapAsync(name => ProblemTag.findOne({ where: { name: name } }))).filter(x => x).map(tag => tag.id);
await problem.setTags(tagIDs);
let download = require('download');
let tmp = require('tmp-promise');
let tmpFile = await tmp.file();
let fs = require('bluebird').promisifyAll(require('fs'));
try {
let data = await download(url.resolve(req.body.url, 'download'));
await fs.writeFileAsync(tmpFile.path, data);
await problem.updateTestdata(tmpFile.path);
} catch (e) {
syzoj.log(e);
} }
res.redirect(syzoj.utils.makeUrl(['problem', problem.id])); res.redirect(syzoj.utils.makeUrl(['problem', problem.id]));
@ -252,6 +353,10 @@ app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) =>
problem.file_io = req.body.io_method === 'file-io'; problem.file_io = req.body.io_method === 'file-io';
problem.file_io_input_name = req.body.file_io_input_name; problem.file_io_input_name = req.body.file_io_input_name;
problem.file_io_output_name = req.body.file_io_output_name; problem.file_io_output_name = req.body.file_io_output_name;
let validateMsg = await problem.validate();
if (validateMsg) throw 'Invalid problem: ' + validateMsg;
if (req.file) { if (req.file) {
await problem.updateTestdata(req.file.path); await problem.updateTestdata(req.file.path);
} }
@ -330,9 +435,12 @@ app.get('/problem/:id/download', async (req, res) => {
await problem.loadRelationships(); await problem.loadRelationships();
if (!problem.testdata) throw 'No testdata';
res.download(problem.testdata.getPath(), `testdata_${id}.zip`); res.download(problem.testdata.getPath(), `testdata_${id}.zip`);
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);
res.status(404);
res.render('error', { res.render('error', {
err: e err: e
}); });

5
package.json

@ -27,10 +27,12 @@
"ansi-to-html": "^0.4.2", "ansi-to-html": "^0.4.2",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"cookie-parser": "^1.4.3", "cookie-parser": "^1.4.3",
"download": "^5.0.3",
"ejs": "^2.5.2", "ejs": "^2.5.2",
"express": "^4.14.0", "express": "^4.14.0",
"express-session": "^1.14.1", "express-session": "^1.14.1",
"file-size": "^1.0.0", "file-size": "^1.0.0",
"fs-extra": "^2.1.2",
"gravatar": "^1.5.2", "gravatar": "^1.5.2",
"moemark-renderer": "^1.2.5", "moemark-renderer": "^1.2.5",
"moment": "^2.15.0", "moment": "^2.15.0",
@ -42,6 +44,7 @@
"sequelize": "^3.24.3", "sequelize": "^3.24.3",
"session-file-store": "^1.0.0", "session-file-store": "^1.0.0",
"sqlite3": "^3.1.4", "sqlite3": "^3.1.4",
"syzoj-divine": "^1.0.0" "syzoj-divine": "^1.0.0",
"tmp-promise": "^1.0.3"
} }
} }

16
views/problem_import.ejs

@ -0,0 +1,16 @@
<% this.title = '导入题目'; %>
<% include header %>
<div class="padding">
<h1 style="margin-bottom: 30px; ">导入题目</h1>
<form class="ui form" action="<%= syzoj.utils.makeUrl(['problem', problem.id, 'import']) %>" method="post">
<div class="field">
<label>题目 ID</label>
<input type="text" value="<%= problem.id ? problem.id : '新题目' %>" disabled>
</div>
<div class="field">
<label>题目链接</label>
<input type="text" name="url" placeholder="如:http://example.com/problem/1">
</div>
<button type="submit" class="ui button">提交</button>
</form>
<% include footer %>

21
views/problems.ejs

@ -8,7 +8,7 @@ if (typeof tags !== 'undefined') tagIDs = tags.map(x => x.id);
<div class="padding"> <div class="padding">
<div class="ui grid"> <div class="ui grid">
<div class="row"> <div class="row">
<div class="eight wide column"> <div class="seven wide column">
<% if (typeof tags !== 'undefined') { %> <% if (typeof tags !== 'undefined') { %>
<% <%
tags.sort((a, b) => { tags.sort((a, b) => {
@ -24,7 +24,7 @@ if (typeof tags !== 'undefined') tagIDs = tags.map(x => x.id);
<% } %> <% } %>
<% } %> <% } %>
</div> </div>
<div class="eight wide right aligned column"> <div class="nine wide right aligned column">
<div class="ui toggle checkbox" id="show_tag"> <div class="ui toggle checkbox" id="show_tag">
<style id="show_tag_style"></style> <style id="show_tag_style"></style>
<script> <script>
@ -55,12 +55,19 @@ if (typeof tags !== 'undefined') tagIDs = tags.map(x => x.id);
<div style="margin-left: 10px; display: inline-block; "> <div style="margin-left: 10px; display: inline-block; ">
<% if (user && user.is_admin) { %> <% if (user && user.is_admin) { %>
<% if (typeof tags !== 'undefined' && tags.length === 1) { %> <% if (typeof tags !== 'undefined' && tags.length === 1) { %>
<a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problems', 'tag', tags[0].id, 'edit']) %>" class="ui mini blue button">编辑标签</a> <a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problems', 'tag', tags[0].id, 'edit']) %>" class="ui labeled icon mini blue button"><i class="write icon"></i> 编辑标签</a>
<% } %> <% } %>
<a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problems', 'tag', 0, 'edit']) %>" class="ui mini green button">添加标签</a> <a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problems', 'tag', 0, 'edit']) %>" class="ui labeled icon mini green button"><i class="plus icon"></i> 添加标签</a>
<% } %> <% } %>
<% if (user) { %> <% if (user) { %>
<a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problem', 0, 'edit']) %>" class="ui mini button">添加题目</a> <div style="margin-left: 10px; " class="ui mini buttons">
<div class="ui labeled icon mini dropdown button" id="add_problem_dropdown"><i class="plus icon"></i> 添加题目
<div class="menu">
<a class="item" href="<%= syzoj.utils.makeUrl(['problem', 0, 'edit']) %>"><i class="file icon"></i> 新建题目</a>
<a class="item" href="<%= syzoj.utils.makeUrl(['problem', 0, 'import']) %>"><i class="cloud download icon"></i> 导入题目</a>
</div>
</div>
</div>
<% } %> <% } %>
</div> </div>
</div> </div>
@ -137,6 +144,10 @@ document.addEventListener('keydown', function (event) {
if (event.keyCode === 39) document.getElementById('page_next').click(); if (event.keyCode === 39) document.getElementById('page_next').click();
else if (event.keyCode === 37) document.getElementById('page_prev').click(); else if (event.keyCode === 37) document.getElementById('page_prev').click();
}); });
$(function () {
$('#add_problem_dropdown').dropdown();
});
</script> </script>
</div> </div>
<% include footer %> <% include footer %>

Loading…
Cancel
Save