Browse Source

Add contest

master
Menci 8 years ago
parent
commit
38a57ec884
  1. 4
      config-example.json
  2. 4
      models/article-comment.js
  3. 2
      models/article.js
  4. 16
      models/common.js
  5. 140
      models/contest.js
  6. 99
      models/contest_player.js
  7. 77
      models/contest_ranklist.js
  8. 25
      models/judge_state.js
  9. 4
      models/problem.js
  10. 2
      models/waiting_judge.js
  11. 52
      modules/api_v2.js
  12. 226
      modules/contest.js
  13. 39
      modules/discussion.js
  14. 2
      modules/index.js
  15. 18
      modules/judge.js
  16. 34
      modules/problem.js
  17. 17
      modules/user.js
  18. 22
      utility.js
  19. 69
      views/contest.ejs
  20. 48
      views/contest_list.ejs
  21. 81
      views/contest_problem.ejs
  22. 63
      views/contest_ranklist.ejs
  23. 50
      views/edit_contest.ejs
  24. 1
      views/header.ejs
  25. 18
      views/judge_detail.ejs
  26. 14
      views/page.ejs
  27. 2
      views/ranklist.ejs

4
config-example.json

@ -25,7 +25,9 @@
"judge_state": 10,
"ranklist": 20,
"discussion": 10,
"article_comment": 10
"article_comment": 10,
"contest": 10,
"edit_contest_problem_list": 10
},
"languages": [
"C++",

4
models/article-comment.js

@ -33,7 +33,7 @@ let model = db.define('comment', {
article_id: {
type: Sequelize.INTEGER,
references: {
model: Article.model,
model: 'article',
key: 'id'
}
},
@ -41,7 +41,7 @@ let model = db.define('comment', {
user_id: {
type: Sequelize.INTEGER,
references: {
model: User.model,
model: 'user',
key: 'id'
}
},

2
models/article.js

@ -33,7 +33,7 @@ let model = db.define('article', {
user_id: {
type: Sequelize.INTEGER,
references: {
model: User.model,
model: 'user',
key: 'id'
}
},

16
models/common.js

@ -87,13 +87,19 @@ class Model {
return this.model.count({ where: where });
}
static async query(page, perpage, where, order) {
let records = await this.model.findAll({
offset: (page - 1) * perpage,
limit: perpage,
static async query(paginate, where, order) {
let options = {
where: where,
order: order
});
};
if (Array.isArray(paginate)) {
options.offset = paginate[0] - 1;
options.limit = paginate[1] - paginate[0] + 1;
} else if (paginate) {
options.offset = (paginate.currPage - 1) * paginate.perPage;
options.limit = paginate.perPage;
}
let records = await this.model.findAll(options);
return records.mapAsync(record => (this.fromRecord(record)));
}
}

140
models/contest.js

@ -0,0 +1,140 @@
/*
* 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 Sequelize = require('sequelize');
let db = syzoj.db;
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let ContestRanklist = syzoj.model('contest_ranklist');
let ContestPlayer = syzoj.model('contest_player');
let model = db.define('contest', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: Sequelize.STRING(80) },
start_time: { type: Sequelize.INTEGER },
end_time: { type: Sequelize.INTEGER },
holder_id: {
type: Sequelize.INTEGER,
references: {
model: 'user',
key: 'id'
}
},
information: { type: Sequelize.TEXT },
problems: { type: Sequelize.TEXT },
ranklist_id: {
type: Sequelize.INTEGER,
references: {
model: 'contest_ranklist',
key: 'id'
}
}
}, {
timestamps: false,
tableName: 'contest',
indexes: [
{
fields: ['holder_id'],
},
{
fields: ['ranklist_id'],
}
]
});
let Model = require('./common');
class Contest extends Model {
static async create(val) {
return Contest.fromRecord(Contest.model.build(Object.assign({
title: '',
start_time: 0,
end_time: 0,
holder: 0,
ranklist_id: 0
}, val)));
}
async loadRelationships() {
this.holder = await User.fromID(this.holder_id);
this.ranklist = await ContestRanklist.fromID(this.ranklist_id);
}
async isAllowedEditBy(user) {
return user && (user.is_admin || this.holder_id === user.id);
}
async isAllowedSeeResultBy(user) {
return (user && (user.is_admin || this.holder_id === user.id)) || !(await this.isRunning());
}
async getProblems() {
return this.problems.split('|').map(x => parseInt(x));
}
async setProblems(s) {
let a = [];
await s.split('|').forEachAsync(async x => {
let problem = await Problem.fromID(x);
if (!problem) return;
a.push(x);
});
this.problems = a.join('|');
}
async newSubmission(judge_state) {
let problems = await this.getProblems();
if (!problems.includes(judge_state.problem_id)) throw 'No such problem in contest.';
let player = await ContestPlayer.findInContest({
contest_id: this.id,
user_id: judge_state.user_id
});
if (!player) {
player = await ContestPlayer.create({
contest_id: this.id,
user_id: judge_state.user_id
});
}
await player.updateScore(judge_state);
await player.save();
await this.loadRelationships();
await this.ranklist.updatePlayer(player);
await this.ranklist.save();
}
async isRunning(now) {
if (!now) now = syzoj.utils.getCurrentTime();
return now >= this.start_time && now < this.end_time;
}
getModel() { return model; }
}
Contest.model = model;
module.exports = Contest;

99
models/contest_player.js

@ -0,0 +1,99 @@
/*
* 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 Sequelize = require('sequelize');
let db = syzoj.db;
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let Contest = syzoj.model('contest');
let model = db.define('contest_player', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
contest_id: {
type: Sequelize.INTEGER,
references: {
model: 'contest',
key: 'id'
}
},
user_id: {
type: Sequelize.INTEGER,
references: {
model: 'user',
key: 'id'
}
},
score: { type: Sequelize.INTEGER },
score_details: { type: Sequelize.TEXT, json: true },
time_spent: { type: Sequelize.INTEGER }
}, {
timestamps: false,
tableName: 'contest_player',
indexes: [
{
fields: ['contest_id'],
},
{
fields: ['user_id'],
}
]
});
let Model = require('./common');
class ContestPlayer extends Model {
static async create(val) {
return ContestPlayer.fromRecord(ContestPlayer.model.build(Object.assign({
contest_id: 0,
user_id: 0,
score: 0,
score_details: '{}',
time_spent: 0
}, val)));
}
static async findInContest(where) {
return ContestPlayer.findOne({ where: where });
}
async loadRelationships() {
this.user = await User.fromID(this.user_id);
this.contest = await Contest.fromID(this.contest_id);
}
async updateScore(judge_state) {
this.score_details[judge_state.problem_id] = {
score: judge_state.score,
judge_id: judge_state.id
};
this.score = 0;
for (let x in this.score_details) {
this.score += this.score_details[x].score;
}
}
getModel() { return model; }
}
ContestPlayer.model = model;
module.exports = ContestPlayer;

77
models/contest_ranklist.js

@ -0,0 +1,77 @@
/*
* 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 Sequelize = require('sequelize');
let db = syzoj.db;
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let ContestPlayer = syzoj.model('contest_player');
let model = db.define('contest_ranklist', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
ranklist: { type: Sequelize.TEXT, json: true }
}, {
timestamps: false,
tableName: 'contest_ranklist'
});
let Model = require('./common');
class ContestRanklist extends Model {
static async create(val) {
return ContestRanklist.fromRecord(ContestRanklist.model.build(Object.assign({
ranklist: '{}'
}, val)));
}
async getPlayers() {
let a = [];
for (let i = 1; i <= this.ranklist.player_num; i++) {
a.push(await ContestPlayer.fromID(this.ranklist[i]));
}
return a;
}
async updatePlayer(player) {
let players = await this.getPlayers(), newPlayer = true;
for (let x of players) {
if (x.user_id === player.user_id) {
newPlayer = false;
break;
}
}
if (newPlayer) {
players.push(player);
}
players.sort((a, b) => b.score - a.score);
this.ranklist = { player_num: players.length };
for (let i = 0; i < players.length; i++) this.ranklist[i + 1] = players[i].id;
}
getModel() { return model; }
}
ContestRanklist.model = model;
module.exports = ContestRanklist;

25
models/judge_state.js

@ -24,6 +24,7 @@ let db = syzoj.db;
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let Contest = syzoj.model('contest');
let model = db.define('judge_state', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
@ -108,13 +109,28 @@ class JudgeState extends Model {
if (user && (user.is_admin || user.id === this.problem.user_id)) return true;
else if (this.type === 0) return true;
else if (this.type === 1) {
// TODO: contest
return false;
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
return false;
} else {
return true;
}
} else if (this.type === 2) return false;
}
async isAllowedSeeCodeBy(user) {
return this.isAllowedSeeResultBy(user);
await this.loadRelationships();
if (user && (user.is_admin || user.id === this.problem.user_id)) return true;
else if (this.type === 0) return true;
else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
return user && this.user_id === user.id;
} else {
return true;
}
} else if (this.type === 2) return false;
}
async updateResult(result) {
@ -131,6 +147,9 @@ class JudgeState extends Model {
if (this.status === 'Accepted') this.problem.ac_num++;
await this.user.save();
await this.problem.save();
} else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
await contest.newSubmission(this);
}
}

4
models/problem.js

@ -32,7 +32,7 @@ let model = db.define('problem', {
user_id: {
type: Sequelize.INTEGER,
references: {
model: User.model,
model: 'user',
key: 'id'
}
},
@ -49,7 +49,7 @@ let model = db.define('problem', {
testdata_id: {
type: Sequelize.INTEGER,
references: {
model: TestData.model,
model: 'file',
key: 'id'
}
},

2
models/waiting_judge.js

@ -29,7 +29,7 @@ let model = db.define('waiting_judge', {
judge_id: {
type: Sequelize.INTEGER,
references: {
model: JudgeState.model,
model: 'judge_state',
key: 'id'
}
}

52
modules/api_v2.js

@ -0,0 +1,52 @@
/*
* 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');
app.get('/api/v2/search/problem/:keyword*?', async (req, res) => {
try {
let keyword = req.params.keyword || '';
let problems = await Problem.query(null, {
title: { like: `%${req.params.keyword}%` }
}, [['id', 'asc']]);
let result = [];
let id = parseInt(keyword);
if (id) {
let problemById = await Problem.fromID(parseInt(keyword));
if (problemById && await problemById.isAllowedUseBy(res.locals.user)) {
result.push(problemById);
}
}
await problems.forEachAsync(async problem => {
if (await problem.isAllowedUseBy(res.locals.user) && result.length < syzoj.config.page.edit_contest_problem_list && problem.id !== id) {
result.push(problem);
}
});
result = result.map(x => ({ name: `#${x.id}. ${x.title}`, value: x.id }));
res.send({ success: true, results: result });
} catch (e) {
syzoj.log(e);
res.send({ success: false });
}
});

226
modules/contest.js

@ -0,0 +1,226 @@
/*
* 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 Contest = syzoj.model('contest');
let ContestRanklist = syzoj.model('contest_ranklist');
let ContestPlayer = syzoj.model('contest_player');
let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state');
let User = syzoj.model('user');
app.get('/contest', async (req, res) => {
try {
let paginate = syzoj.utils.paginate(Contest.count(), req.query.page, syzoj.config.page.contest);
let contests = await Contest.query(paginate);
await contests.forEachAsync(async x => x.information = await syzoj.utils.markdown(x.information));
res.render('contest_list', {
contests: contests,
paginate: paginate
})
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/contest/:id/edit', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw 'Permission denied.';
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!contest) {
contest = await Contest.create();
}
let problems = await contest.problems.split('|').mapAsync(async id => await Problem.fromID(id));
res.render('edit_contest', {
contest: contest,
problems: problems
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.post('/contest/:id/edit', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw 'Permission denied.';
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!contest) {
contest = await Contest.create();
contest.holder_id = res.locals.user.id;
let ranklist = await ContestRanklist.create();
await ranklist.save();
contest.ranklist_id = ranklist.id;
}
contest.title = req.body.title;
if (!Array.isArray(req.body.problems)) req.body.problems = [req.body.problems];
contest.problems = req.body.problems.join('|');
contest.information = req.body.information;
contest.start_time = syzoj.utils.parseTime(req.body.start_time);
contest.end_time = syzoj.utils.parseTime(req.body.end_time);
await contest.save();
res.redirect(syzoj.utils.makeUrl(['contest', contest.id]));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/contest/:id', async (req, res) => {
try {
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!contest) throw 'No such contest.';
contest.allowedEdit = await contest.isAllowedEditBy(res.locals.user);
contest.running = await contest.isRunning();
contest.information = await syzoj.utils.markdown(contest.information);
let problems_id = await contest.getProblems();
let problems = await problems_id.mapAsync(async id => await Problem.fromID(id));
let player = null;
if (res.locals.user) {
player = await ContestPlayer.findInContest({
contest_id: contest.id,
user_id: res.locals.user.id
});
}
problems = problems.map(x => ({ problem: x, status: null, judge_id: null }));
if (player) {
for (let problem of problems) {
if (player.score_details[problem.problem.id]) {
if (await contest.isRunning()) {
problem.status = true;
} else {
let judge_state = await JudgeState.fromID(player.score_details[problem.problem.id].judge_id);
problem.status = judge_state.status;
}
problem.judge_id = player.score_details[problem.problem.id].judge_id;
} else {
if (contest.isRunning()) {
problem.status = false;
}
}
}
}
res.render('contest', {
contest: contest,
problems: problems
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/contest/:id/ranklist', async (req, res) => {
try {
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!contest) throw 'No such contest.';
if (!await contest.isAllowedSeeResultBy(res.locals.user)) throw 'Permission denied';
await contest.loadRelationships();
let players_id = [];
for (let i = 1; i <= contest.ranklist.ranklist.player_num; i++) players_id.push(contest.ranklist.ranklist[i]);
let ranklist = await players_id.mapAsync(async player_id => {
let player = await ContestPlayer.fromID(player_id);
let user = await User.fromID(player.user_id);
return {
user: user,
player: player
};
});
let problems_id = await contest.getProblems();
let problems = await problems_id.mapAsync(async id => await Problem.fromID(id));
res.render('contest_ranklist', {
contest: contest,
ranklist: ranklist,
problems: problems
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/contest/:id/:pid', async (req, res) => {
try {
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!await contest.isRunning()) throw 'The contest has ended.'
let problems_id = await contest.getProblems();
let pid = parseInt(req.params.pid);
if (!pid || pid < 1 || pid > problems_id.length) throw 'No such problem.';
let problem_id = problems_id[pid - 1];
let problem = await Problem.fromID(problem_id);
await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]);
res.render('contest_problem', {
contest: contest,
problem: problem
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});

39
modules/discussion.js

@ -25,21 +25,14 @@ let User = syzoj.model('user');
app.get('/discussion', async (req, res) => {
try {
let page = parseInt(req.query.page);
if (!page || page < 1) page = 1;
let count = await Article.count();
let pageCnt = Math.ceil(count / syzoj.config.page.discussion);
if (page > pageCnt) page = pageCnt;
let articles = await Article.query(page, syzoj.config.page.discussion, null, [['public_time', 'asc']]);
let paginate = syzoj.utils.paginate(await Article.count(), req.query.page, syzoj.config.page.discussion);
let articles = await Article.query(paginate, null, [['public_time', 'asc']]);
for (let article of articles) await article.loadRelationships();
res.render('discussion', {
articles: articles,
pageCnt: pageCnt,
page: page
paginate: paginate
});
} catch (e) {
syzoj.log(e);
@ -62,28 +55,20 @@ app.get('/article/:id', async (req, res) => {
let where = { article_id: id };
let page = parseInt(req.query.page);
if (!page || page < 1) page = 1;
let count = await ArticleComment.count(where);
let pageCnt = Math.ceil(count / syzoj.config.page.article_comment);
if (page > pageCnt) page = pageCnt;
let comments = [];
if (count) {
comments = await ArticleComment.query(page, syzoj.config.page.article_comment, where, [['public_time', 'asc']]);
for (let comment of comments) {
comment.content = await syzoj.utils.markdown(comment.content);
comment.allowedEdit = await comment.isAllowedEditBy(res.locals.user);
await comment.loadRelationships();
}
let paginate = syzoj.utils.paginate(await ArticleComment.count(where), req.query.page, syzoj.config.page.article_comment);
let comments = await ArticleComment.query(paginate, where, [['public_time', 'asc']]);
for (let comment of comments) {
comment.content = await syzoj.utils.markdown(comment.content);
comment.allowedEdit = await comment.isAllowedEditBy(res.locals.user);
await comment.loadRelationships();
}
res.render('article', {
article: article,
comments: comments,
pageCnt: pageCnt,
page: page
paginate: paginate
});
} catch (e) {
syzoj.log(e);

2
modules/index.js

@ -25,7 +25,7 @@ let Divine = require('syzoj-divine');
app.get('/', async (req, res) => {
try {
let ranklist = await User.query(1, 10, { is_show: true }, [['ac_num', 'desc']]);
let ranklist = await User.query([1, 10], { is_show: true }, [['ac_num', 'desc']]);
await ranklist.forEachAsync(async x => x.renderInformation());
let notices = await syzoj.config.notices.mapAsync(async notice => {

18
modules/judge.js

@ -24,28 +24,20 @@ let User = syzoj.model('user');
app.get('/judge_state', async (req, res) => {
try {
let page = parseInt(req.query.page);
if (!page || page < 1) page = 1;
let user = await User.fromName(req.query.submitter || '');
let where = {};
if (user) where.user_id = user.id;
if (req.query.problem_id) where.problem_id = parseInt(req.query.problem_id);
let count = await JudgeState.count(where);
let pageCnt = Math.ceil(count / syzoj.config.page.judge_state);
if (page > pageCnt) page = pageCnt;
let judge_state = await JudgeState.query(page, syzoj.config.page.judge_state, where, [['submit_time', 'desc']]);
let paginate = syzoj.utils.paginate(await JudgeState.count(where), req.query.page, syzoj.config.page.judge_state);
let judge_state = await JudgeState.query(paginate, where, [['submit_time', 'desc']]);
await judge_state.forEachAsync(async obj => obj.hidden = !(await obj.isAllowedSeeResultBy(res.locals.user)));
await judge_state.forEachAsync(async obj => obj.allowedSeeCode = await obj.isAllowedSeeCodeBy(res.locals.user));
res.render('judge_state', {
judge_state: judge_state,
pageCnt: pageCnt,
page: page,
paginate: paginate,
form: {
submitter: req.query.submitter || '',
problem_id: req.query.problem_id || ''
@ -64,10 +56,10 @@ app.get('/judge_detail/:id', async (req, res) => {
let id = parseInt(req.params.id);
let judge = await JudgeState.fromID(id);
if (!await judge.isAllowedSeeCodeBy(res.locals.user)) throw 'Permission denied';
judge.code = await syzoj.utils.highlight(judge.code, judge.language);
if (judge.result.compiler_output) judge.result.compiler_output = syzoj.utils.ansiToHTML(judge.result.compiler_output);
judge.allowedSeeResult = await judge.isAllowedSeeResultBy(res.locals.user);
judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user);
res.render('judge_detail', {
judge: judge

34
modules/problem.js

@ -22,17 +22,12 @@
let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state');
let WaitingJudge = syzoj.model('waiting_judge');
let Contest = syzoj.model('contest');
app.get('/problem', async (req, res) => {
try {
let page = parseInt(req.query.page);
if (!page || page < 1) page = 1;
let count = await Problem.count();
let pageCnt = Math.ceil(count / syzoj.config.page.problem);
if (page > pageCnt) page = pageCnt;
let problems = await Problem.query(page, syzoj.config.page.problem);
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);
@ -41,8 +36,7 @@ app.get('/problem', async (req, res) => {
res.render('problem_set', {
problems: problems,
pageCnt: pageCnt,
page: page
paginate: paginate
});
} catch (e) {
syzoj.log(e);
@ -221,11 +215,25 @@ app.post('/submit/:id', async (req, res) => {
code: req.body.code,
language: req.body.language,
user_id: res.locals.user.id,
problem_id: req.params.id,
type: problem.is_public ? 0 : 2
problem_id: req.params.id
});
await judge_state.save();
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();
await judge_state.updateRelatedInfo();
} else {
judge_state.type = problem.is_public ? 0 : 2;
await judge_state.save();
}
let waiting_judge = await WaitingJudge.create({
judge_id: judge_state.id

17
modules/user.js

@ -24,23 +24,13 @@ let User = syzoj.model('user');
// Ranklist
app.get('/ranklist', async (req, res) => {
try {
let page = parseInt(req.query.page);
if (!page || page < 1) page = 1;
let count = await User.count({
is_show: true
});
let pageCnt = Math.ceil(count / syzoj.config.page.ranklist);
if (page > pageCnt) page = pageCnt;
let ranklist = await User.query(page, syzoj.config.page.ranklist, { is_show: true }, [['ac_num', 'desc']]);
let paginate = syzoj.utils.paginate(await User.count({ is_show: true }), req.query.page, syzoj.config.page.ranklist);
let ranklist = await User.query(paginate, { is_show: true }, [['ac_num', 'desc']]);
await ranklist.forEachAsync(async x => x.renderInformation());
res.render('ranklist', {
ranklist: ranklist,
pageCnt: pageCnt,
page: page
paginate: paginate
});
} catch (e) {
console.log(e);
@ -98,6 +88,7 @@ app.get('/user/:id', async (req, res) => {
let user = await User.fromID(id);
user.ac_problems = await user.getACProblems();
user.articles = await user.getArticles();
user.allowedEdit = await user.isAllowedEditBy(res.locals.user);
res.render('user', {
show_user: user

22
utility.js

@ -78,10 +78,9 @@ module.exports = {
resolve(replaceUI(s));
});
} else {
let res = obj, cnt = 0;
let res = obj, cnt = keys.length;
for (let key of keys) {
if (res[key].trim()) {
cnt++;
renderer(res[key], (s) => {
res[key] = replaceUI(s);
if (!--cnt) resolve(res);
@ -96,6 +95,9 @@ module.exports = {
m.locale('zh-cn');
return m.format(format || 'L H:mm:ss');
},
parseTime(s) {
return parseInt(+new Date(s) / 1000);
},
getCurrentTime() {
return parseInt(+new Date / 1000);
},
@ -151,5 +153,21 @@ module.exports = {
let Convert = require('ansi-to-html');
let convert = new Convert({ escapeXML: true });
return convert.toHtml(s);
},
paginate(count, currPage, perPage) {
currPage = parseInt(currPage);
if (!currPage || currPage < 1) currPage = 1;
let pageCnt = Math.ceil(count / perPage);
if (currPage > pageCnt) currPage = pageCnt;
return {
currPage: currPage,
perPage: perPage,
pageCnt: pageCnt
};
},
removeTitleTag(s) {
return s.replace(/「[\S\s]+?」/, '');
}
};

69
views/contest.ejs

@ -0,0 +1,69 @@
<% this.title = contest.title + ' - 比赛' %>
<% include header %>
<div class="padding">
<h1 style="margin-bottom: 30px; "><%= contest.title %></h1>
<div class="ui grid">
<% if (contest.allowedEdit || !contest.running) { %>
<div class="row">
<div class="column">
<div class="ui buttons">
<a class="ui small positive button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, 'ranklist']) %>">排行榜</a>
<% if (contest.allowedEdit) { %>
<a class="ui small button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, 'edit']) %>">编辑比赛</a>
<% } %>
</div>
</div>
</div>
<% } %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">描述</h4>
<div class="ui bottom attached segment">
<%- contest.information %>
</div>
</div>
</div>
<div class="row">
<div class="column">
<table class="ui selectable celled table">
<thead>
<tr>
<th class="one wide" style="text-align: center">状态</th>
<th class="fourteen wide">题目</th>
<th class="one wide" style="text-align: center">代码</th>
</tr>
</thead>
<tbody>
<%
let i = 0;
for (let problem of problems) {
i++;
%>
<tr>
<td class="center aligned" style="white-space: nowrap; ">
<% if (problem.judge_id) { %>
<a href="<%= syzoj.utils.makeUrl(['judge_detail', problem.judge_id]) %>">
<% if (problem.status === true) { %>
<i class="black checkmark icon"></i>
<% } else if (problem.status !== false) { %>
<span class="status <%= problem.status.toLowerCase().split(' ').join('_') %>">
<%= problem.status %>
</span>
<% } %>
<% } %>
</td>
<td><a href="<%= syzoj.utils.makeUrl(['contest', contest.id, i]) %>"><%= syzoj.utils.removeTitleTag(problem.problem.title) %></a></td>
<td class="center aligned">
<% if (problem.judge_id) { %>
<a href="<%= syzoj.utils.makeUrl(['judge_detail', problem.judge_id]) %>"><i style="color: #000;" class="code icon"></i></a>
<% } %>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
</div>
</div>
<% include footer %>

48
views/contest_list.ejs

@ -0,0 +1,48 @@
<% this.title = '比赛' %>
<% include header %>
<div class="padding">
<% if (user && user.is_admin) { %>
<div class="ui grid">
<div class="row">
<div class="column">
<a href="<%= syzoj.utils.makeUrl(['contest', 0, 'edit']) %>" class="ui mini right floated button">添加比赛</a>
</div>
</div>
</div>
<% } %>
<table class="ui very basic center aligned table">
<thead>
<tr>
<th>比赛名称</th>
<th>开始时间</th>
<th>结束时间</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<%
for (let contest of contests) {
let now = syzoj.utils.getCurrentTime();
let tag = '';
%>
<tr>
<% if (now < contest.start_time) { %>
<% tag = '<span class="ui header"><div class="ui mini grey label">未开始</div></span>' %>
<% } else if (now >= contest.start_time && now < contest.end_time) { %>
<% tag = '<span class="ui header"><div class="ui mini green label">进行中</div></span>' %>
<% } else { %>
<% tag = '<span class="ui header"><div class="ui mini red label">已结束</div></span>' %>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['contest', contest.id]) %>"><%= contest.title %> <%- tag %></a></td>
<td><%= syzoj.utils.formatDate(contest.start_time) %></td>
<td><%= syzoj.utils.formatDate(contest.end_time) %></td>
<td><%- contest.information %></td>
</tr>
<% } %>
</tbody>
</table>
<br>
<% include page %>
</div>
<% include footer %>

81
views/contest_problem.ejs

@ -0,0 +1,81 @@
<% this.title = syzoj.utils.removeTitleTag(problem.title) + ' - ' + contest.title + ' - 比赛' %>
<% include header %>
<div class="ui center aligned grid">
<div class="row">
<h1><%= syzoj.utils.removeTitleTag(problem.title) %></h1>
</div>
<div class="row" style="margin-top: -15px">
<span class="ui label">内存限制: <%= problem.memory_limit %> MiB</span>
<span class="ui label">时间限制: <%= problem.time_limit %> ms</span>
</div>
<div class="row" style="margin-top: -23px">
<% if (problem.file_io) { %>
<span class="ui label">输入文件: <%= problem.file_io_input_name %></span>
<span class="ui label">输出文件: <%= problem.file_io_output_name %></span>
<% } else { %>
<span class="ui label">标准输入输出</span>
<% } %>
</div>
</div>
<div class="ui grid">
<div class="row">
<div class="column">
<div class="ui buttons">
<a href="#submit_form" class="ui primary button">提交</a>
<a href="<%= syzoj.utils.makeUrl(['contest', contest.id]) %>" class="ui positive button">返回比赛</a>
</div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">题目描述</h4>
<div class="ui bottom attached segment" id="description"><%- problem.description %></div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">输入格式</h4>
<div class="ui bottom attached segment" id="input"><%- problem.input_format %></div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">输出格式</h4>
<div class="ui bottom attached segment" id="output"><%- problem.output_format %></div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">测试样例</h4>
<div class="ui bottom attached segment" id="example"><%- problem.example %></div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">数据范围与提示</h4>
<div class="ui bottom attached segment" id="hint"><%- problem.limit_and_hint %></div>
</div>
</div>
</div>
<h2 class="ui header">提交代码</h2>
<form id="submit_form" class="ui form" action="<%= syzoj.utils.makeUrl(['submit', problem.id], { contest_id: contest.id }) %>" method="post">
<div class="field">
<label for="doc-select-1">选择语言</label>
<select class="ui fluid dropdown" name="language" id="doc-select-1">
<% for (lang of syzoj.config.languages) { %>
<option value="<%= lang %>"><%= lang %></option>
<% } %>
</select>
</div>
<div class="field">
<textarea rows="15" name="code" style="font-family: 'Roboto Mono', 'Bitstream Vera Sans Mono', 'Menlo', 'Consolas', 'Lucida Console', monospace; "></textarea>
</div>
<button type="submit" class="ui button">提交</button>
</form>
<script>
$(function () {
$('.ui.dropdown').dropdown();
});
</script>
<% include footer %>

63
views/contest_ranklist.ejs

@ -0,0 +1,63 @@
<%= this.title = '排名 - ' + contest.title %>
<% include header %>
<div class="padding">
<table class="ui very basic center aligned table">
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<% for (let problem of problems) { %>
<th><a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>">
<%= syzoj.utils.removeTitleTag(problem.title) %>
</a></th>
<% } %>
<th>总分</th>
</tr>
</thead>
<tbody>
<%
let i = 0;
for (let item of ranklist) {
i++
%>
<tr>
<td>
<% if (i == 1) { %>
<div class="ui yellow ribbon label">
<% } else if (i == 2) { %>
<div class="ui ribbon label">
<% } else if (i == 3) { %>
<div class="ui brown ribbon label" style="background-color: #C47222 !important;">
<% } else { %>
<div>
<% } %>
<%= i %>
</div>
</td>
<td><a href="<%= syzoj.utils.makeUrl(['user', item.user.id]) %>"><%= item.user.username %></a>
<% if (item.user.nameplate) { %>
<%= item.user.nameplate %>
<% } %>
</td>
<% for (let problem of problems) { %>
<% if (item.player.score_details[problem.id]) { %>
<td><a href="<%= syzoj.utils.makeUrl(['judge_detail', item.player.score_details[problem.id].judge_id]) %>">
<span class="score score_<%= parseInt((item.player.score_details[problem.id].score / 10) || 0) %>">
<%= item.player.score_details[problem.id].score %>
</span>
</a></td>
<% } else { %>
<td></td>
<% } %>
<% } %>
<td>
<span class="score score_<%= parseInt((item.player.score / ranklist[0].player.score * 10) || 0) %>">
<%= item.player.score %>
</span>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
<% include footer %>

50
views/edit_contest.ejs

@ -0,0 +1,50 @@
<% this.title = contest.id ? '编辑比赛' : '新建比赛' %>
<% include header %>
<div class="padding">
<form class="ui form" action="<%= syzoj.utils.makeUrl(['contest', contest.id, 'edit']) %>" method="post">
<div class="field">
<label>比赛名称</label>
<input type="text" name="title" value="<%= contest.title %>">
</div>
<div class="field">
<label>试题列表</label>
<select class="ui fluid search dropdown" multiple="" id="search_problems" name="problems">
<% for (let problem of problems) { %>
<option value="<%= problem.id %>" selected>#<%= problem.id %>. <%= problem.title %></option>
<% } %>
</select>
</div>
<div class="field">
<label>比赛介绍</label>
<textarea class="" rows="5" id="doc-ta-1" name="information"><%= contest.information %></textarea>
</div>
<div class="field">
<label>开始时间</label>
<input type="text" name="start_time" value="<%= syzoj.utils.formatDate(contest.start_time || syzoj.utils.getCurrentTime()) %>">
</div>
<div class="field">
<label>结束时间</label>
<input type="text" name="end_time" value="<%= syzoj.utils.formatDate(contest.end_time || syzoj.utils.getCurrentTime()) %>">
</div>
<button type="submit" class="ui button">提交</button>
</form>
<script>
$(function () {
$('#search_problems')
.dropdown({
debug: true,
apiSettings: {
url: '/api/v2/search/problem/{query}',
onResponse: function (response) {
var a = $('#search_problems').val().map(x => parseInt(x));
if (response.results) {
response.results = response.results.filter(x => !a.includes(parseInt(x.value)));
}
return response;
},
cache: false
}
});
});
</script>
<% include footer %>

1
views/header.ejs

@ -17,6 +17,7 @@
<a class="header item" href="/"><span style="font-family: Raleway; font-size: 1.5em; font-weight: 500; "><%= syzoj.config.title %></span></a>
<a class="item<% if (active === '') { %> active<% } %>" href="/"><i class="home icon"></i> 首页</a>
<a class="item<% if (active === 'problem') { %> active<% } %>" href="/problem"><i class="list icon"></i> 题库</a>
<a class="item<% if (active === 'contest') { %> active<% } %>" href="/contest"><i class="calendar icon"></i> 比赛</a>
<a class="item<% if (active.startsWith('judge')) { %> active<% } %>" href="/judge_state"><i class="tasks icon"></i> 评测</a>
<a class="item<% if (active === 'ranklist') { %> active<% } %>" href="/ranklist"><i class="signal icon"></i> 排名</a>
<a class="item<% if (active === 'discussion' || active === 'article') { %> active<% } %>" href="/discussion"><i class="comments icon"></i> 讨论</a>

18
views/judge_detail.ejs

@ -17,19 +17,27 @@
<tr>
<td>#<%= judge.id %></td>
<td><a href="<%= syzoj.utils.makeUrl(['problem', judge.problem_id]) %>">#<%= judge.problem_id %>. <%= judge.problem.title %></a></td>
<td class="status <%= judge.result.status.toLowerCase().split(' ').join('_') %>"><%= judge.result.status %></td>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<% if (judge.allowedSeeResult) { %>
<td class="status <%= judge.result.status.toLowerCase().split(' ').join('_') %>"><%= judge.result.status %></td>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<td><%= judge.result.total_time %></td>
<% } else { %>
<td>隐藏</td>
<td>隐藏</td>
<td>隐藏</td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
</tr>
</tbody>
</table>
<% if (judge.allowedSeeCode) { %>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- judge.code %></code></pre></div>
<% if (judge.result.compiler_output && judge.result.status === 'Compile Error') { %>
<% } %>
<% if (judge.result.compiler_output && judge.result.status === 'Compile Error' && judge.allowedSeeCode) { %>
<h3 class="ui header">编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- judge.result.compiler_output %></code></pre></div>
<% } else { %>
<% } else if (judge.allowedSeeResult) { %>
<div class="ui styled fluid accordion">
<% for (let i = 0; i < judge.result.case_num; i++) { %>
<% let testcase = judge.result[i]; %>
@ -56,7 +64,7 @@
<% } %>
</div>
<% } %>
<script>
$(function() {
$('.ui.accordion').accordion();

14
views/page.ejs

@ -1,11 +1,11 @@
<% if (pageCnt) { %>
<% if (paginate.pageCnt) { %>
<div class="ui center aligned grid">
<div class="ui buttons">
<a class="ui<% if (page === 1) { %> disabled<% } %> button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: page - 1 })) %>">
<a class="ui<% if (paginate.currPage === 1) { %> disabled<% } %> button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: paginate.currPage - 1 })) %>">
<i class="left chevron icon"></i>
</a>
<%
let leftCnt = page - 1, rightCnt = pageCnt - page, omitLeft = leftCnt > 4, omitRight = rightCnt > 4;
let leftCnt = paginate.currPage - 1, rightCnt = paginate.pageCnt - paginate.currPage, omitLeft = leftCnt > 4, omitRight = rightCnt > 4;
if (omitLeft) leftCnt = 3;
if (omitRight) rightCnt = 3;
if (omitLeft) {
@ -13,13 +13,13 @@
<li><span>...</span></li>
<%
}
for (let i = page - leftCnt; i < page; i++) { %>
for (let i = paginate.currPage - leftCnt; i < paginate.currPage; i++) { %>
<a class="ui button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: i })) %>"><%= i %></a>
<%
}
%>
<a class="ui primary button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: page })) %>"><%= page %></a>
<% for (let i = page + 1; i <= page + rightCnt; i++) { %>
<a class="ui primary button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: paginate.currPage })) %>"><%= paginate.currPage %></a>
<% for (let i = paginate.currPage + 1; i <= paginate.currPage + rightCnt; i++) { %>
<a class="ui button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: i })) %>"><%= i %></a>
<%
}
@ -27,7 +27,7 @@
%>
<li><span>...</span></li>
<% } %>
<a class="ui<% if (page === pageCnt) { %> disabled<% } %> button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: page + 1 })) %>">
<a class="ui<% if (paginate.currPage === paginate.pageCnt) { %> disabled<% } %> button" href="<%= syzoj.utils.makeUrl(req, Object.assign(req.query, { page: paginate.currPage + 1 })) %>">
<i class="right chevron icon"></i>
</a>
</div>

2
views/ranklist.ejs

@ -18,7 +18,7 @@
</thead>
<tbody>
<%
let i = (page - 1) * syzoj.config.page.ranklist;
let i = (paginate.currPage - 1) * paginate.perPage;
for (let user of ranklist) {
++i;
%>

Loading…
Cancel
Save