|
|
|
/*
|
|
|
|
* This file is part of SYZOJ.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
|
|
|
|
*
|
|
|
|
* SYZOJ is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* SYZOJ is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public
|
|
|
|
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
let Problem = syzoj.model('problem');
|
|
|
|
let JudgeState = syzoj.model('judge_state');
|
|
|
|
let WaitingJudge = syzoj.model('waiting_judge');
|
|
|
|
let Contest = syzoj.model('contest');
|
|
|
|
let ProblemTag = syzoj.model('problem_tag');
|
|
|
|
let ProblemTagMap = syzoj.model('problem_tag_map');
|
|
|
|
|
|
|
|
app.get('/problems', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let paginate = syzoj.utils.paginate(await Problem.count(), req.query.page, syzoj.config.page.problem);
|
|
|
|
let problems = await Problem.query(paginate);
|
|
|
|
|
|
|
|
await problems.forEachAsync(async problem => {
|
|
|
|
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
|
|
|
|
problem.judge_state = await problem.getJudgeState(res.locals.user, true);
|
|
|
|
problem.tags = await problem.getTags();
|
|
|
|
});
|
|
|
|
|
|
|
|
res.render('problems', {
|
|
|
|
problems: problems,
|
|
|
|
paginate: paginate
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problems/tag/:tagIDs', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let tagIDs = Array.from(new Set(req.params.tagIDs.split(',').map(x => parseInt(x))));
|
|
|
|
let tags = await tagIDs.mapAsync(async tagID => ProblemTag.fromID(tagID));
|
|
|
|
|
|
|
|
// Validate the tagIDs
|
|
|
|
for (let tag of tags) {
|
|
|
|
if (!tag) {
|
|
|
|
return res.redirect(syzoj.utils.makeUrl(['problems']));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let sql = 'SELECT * FROM `problem` WHERE\n';
|
|
|
|
for (let tagID of tagIDs) {
|
|
|
|
if (tagID !== tagIDs[0]) {
|
|
|
|
sql += 'AND\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
sql += '`problem`.`id` IN (SELECT `problem_id` FROM `problem_tag_map` WHERE `tag_id` = ' + tagID + ')';
|
|
|
|
}
|
|
|
|
|
|
|
|
let paginate = syzoj.utils.paginate(await Problem.count(sql), req.query.page, syzoj.config.page.problem);
|
|
|
|
let problems = await Problem.query(sql);
|
|
|
|
|
|
|
|
await problems.forEachAsync(async problem => {
|
|
|
|
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
|
|
|
|
problem.judge_state = await problem.getJudgeState(res.locals.user, true);
|
|
|
|
problem.tags = await problem.getTags();
|
|
|
|
});
|
|
|
|
|
|
|
|
res.render('problems', {
|
|
|
|
problems: problems,
|
|
|
|
tags: tags,
|
|
|
|
paginate: paginate
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problem/:id', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
if (!problem) throw 'No such problem.';
|
|
|
|
|
|
|
|
if (!await problem.isAllowedUseBy(res.locals.user)) {
|
|
|
|
throw 'Permission denied.';
|
|
|
|
}
|
|
|
|
|
|
|
|
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
|
|
|
|
|
|
|
|
if (problem.is_public || problem.allowedEdit) {
|
|
|
|
await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]);
|
|
|
|
} else {
|
|
|
|
throw 'Permission denied';
|
|
|
|
}
|
|
|
|
|
|
|
|
let state = await problem.getJudgeState(res.locals.user, false);
|
|
|
|
|
|
|
|
problem.tags = await problem.getTags();
|
|
|
|
|
|
|
|
res.render('problem', {
|
|
|
|
problem: problem,
|
|
|
|
state: state
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problem/:id/edit', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id) || 0;
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) {
|
|
|
|
if (!res.locals.user) throw 'Permission denied.';
|
|
|
|
problem = await Problem.create();
|
|
|
|
problem.id = id;
|
|
|
|
problem.allowedEdit = true;
|
|
|
|
problem.tags = [];
|
|
|
|
} else {
|
|
|
|
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied.';
|
|
|
|
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
|
|
|
|
problem.tags = await problem.getTags();
|
|
|
|
}
|
|
|
|
|
|
|
|
res.render('problem_edit', {
|
|
|
|
problem: problem
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.post('/problem/:id/edit', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
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.';
|
|
|
|
}
|
|
|
|
|
|
|
|
problem.title = req.body.title;
|
|
|
|
problem.description = req.body.description;
|
|
|
|
problem.input_format = req.body.input_format;
|
|
|
|
problem.output_format = req.body.output_format;
|
|
|
|
problem.example = req.body.example;
|
|
|
|
problem.limit_and_hint = req.body.limit_and_hint;
|
|
|
|
|
|
|
|
// Save the problem first, to have the `id` allocated
|
|
|
|
await problem.save();
|
|
|
|
|
|
|
|
if (!req.body.tags) {
|
|
|
|
req.body.tags = [];
|
|
|
|
} else if (!Array.isArray(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 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: id,
|
|
|
|
tag_id: tagID
|
|
|
|
} });
|
|
|
|
|
|
|
|
await map.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let tagID of addTagIDs) {
|
|
|
|
let map = await ProblemTagMap.create({
|
|
|
|
problem_id: id,
|
|
|
|
tag_id: tagID
|
|
|
|
});
|
|
|
|
|
|
|
|
await map.save();
|
|
|
|
}
|
|
|
|
|
|
|
|
res.redirect(syzoj.utils.makeUrl(['problem', problem.id]));
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problem/:id/data', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) throw 'No such problem';
|
|
|
|
if (!await problem.isAllowedEditBy(res.locals.user)) throw 'Permission denied';
|
|
|
|
|
|
|
|
await problem.loadRelationships();
|
|
|
|
|
|
|
|
res.render('problem_data', {
|
|
|
|
problem: problem
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) throw 'No such problem';
|
|
|
|
if (!await problem.isAllowedEditBy(res.locals.user)) throw 'Permission denied';
|
|
|
|
|
|
|
|
await problem.loadRelationships();
|
|
|
|
|
|
|
|
problem.time_limit = req.body.time_limit;
|
|
|
|
problem.memory_limit = req.body.memory_limit;
|
|
|
|
problem.file_io = req.body.io_method === 'file-io';
|
|
|
|
problem.file_io_input_name = req.body.file_io_input_name;
|
|
|
|
problem.file_io_output_name = req.body.file_io_output_name;
|
|
|
|
if (req.file) {
|
|
|
|
await problem.updateTestdata(req.file.path);
|
|
|
|
}
|
|
|
|
|
|
|
|
await problem.save();
|
|
|
|
|
|
|
|
res.redirect(syzoj.utils.makeUrl(['problem', id, 'data']));
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.post('/problem/:id/submit', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) throw 'No such problem.';
|
|
|
|
if (!syzoj.config.languages[req.body.language]) throw 'No such language.'
|
|
|
|
if (!res.locals.user) throw 'Please login.';
|
|
|
|
|
|
|
|
let judge_state = await JudgeState.create({
|
|
|
|
code: req.body.code,
|
|
|
|
language: req.body.language,
|
|
|
|
user_id: res.locals.user.id,
|
|
|
|
problem_id: req.params.id
|
|
|
|
});
|
|
|
|
|
|
|
|
let contest_id = parseInt(req.query.contest_id);
|
|
|
|
if (contest_id) {
|
|
|
|
let contest = await Contest.fromID(contest_id);
|
|
|
|
if (!contest) throw 'No such contest.';
|
|
|
|
let problems_id = await contest.getProblems();
|
|
|
|
if (!problems_id.includes(id)) throw 'No such problem.';
|
|
|
|
|
|
|
|
judge_state.type = 1;
|
|
|
|
judge_state.type_info = contest_id;
|
|
|
|
|
|
|
|
await judge_state.save();
|
|
|
|
} else {
|
|
|
|
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied.';
|
|
|
|
judge_state.type = problem.is_public ? 0 : 2;
|
|
|
|
await judge_state.save();
|
|
|
|
}
|
|
|
|
await judge_state.updateRelatedInfo(true);
|
|
|
|
|
|
|
|
let waiting_judge = await WaitingJudge.create({
|
|
|
|
judge_id: judge_state.id
|
|
|
|
});
|
|
|
|
|
|
|
|
await waiting_judge.save();
|
|
|
|
|
|
|
|
if (contest_id) {
|
|
|
|
res.redirect(syzoj.utils.makeUrl(['contest', contest_id]));
|
|
|
|
} else {
|
|
|
|
res.redirect(syzoj.utils.makeUrl(['submission', judge_state.id]));
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problem/:id/download', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) throw 'No such problem';
|
|
|
|
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied';
|
|
|
|
|
|
|
|
await problem.loadRelationships();
|
|
|
|
|
|
|
|
res.download(problem.testdata.getPath(), `testdata_${id}.zip`);
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
app.get('/problem/:id/statistics/:type', async (req, res) => {
|
|
|
|
try {
|
|
|
|
let id = parseInt(req.params.id);
|
|
|
|
let problem = await Problem.fromID(id);
|
|
|
|
|
|
|
|
if (!problem) throw 'No such problem';
|
|
|
|
if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied';
|
|
|
|
|
|
|
|
let count = await problem.countStatistics(req.params.type);
|
|
|
|
if (count === null) throw 'No such type';
|
|
|
|
|
|
|
|
let paginate = syzoj.utils.paginate(count, req.query.page, syzoj.config.page.problem_statistics);
|
|
|
|
let statistics = await problem.getStatistics(req.params.type, paginate);
|
|
|
|
|
|
|
|
await statistics.judge_state.forEachAsync(async x => x.loadRelationships());
|
|
|
|
|
|
|
|
res.render('statistics', {
|
|
|
|
statistics: statistics,
|
|
|
|
paginate: paginate,
|
|
|
|
problem: problem
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
syzoj.log(e);
|
|
|
|
res.render('error', {
|
|
|
|
err: e
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|