From 89bac68583e4d36d1cce9c7e7a7d26bcf397d97f Mon Sep 17 00:00:00 2001 From: Menci Date: Sun, 16 Apr 2017 16:11:36 +0800 Subject: [PATCH] Allow admin to change problem id --- README.md | 2 +- app.js | 15 ++++++++++++--- models/article-comment.js | 16 ++-------------- models/article.js | 8 +------- models/common.js | 4 ++++ models/contest.js | 6 +++++- models/contest_player.js | 16 ++-------------- models/judge_state.js | 16 ++-------------- models/problem.js | 33 ++++++++++++++++++++++++++++++++- models/problem_tag_map.js | 6 +----- models/waiting_judge.js | 8 +------- modules/problem.js | 8 ++++++++ views/problem_edit.ejs | 16 +++++++++++++--- 13 files changed, 84 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 243e2f4..4fb62b9 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Install dependencies with `npm install` or `yarn`. Copy the `config-example.json` file to `config.json`, and change the configures. ## Database -SYZOJ 2 uses [Sequelize](http://sequelizejs.com), which supports many database systems, including MySQL and Sqlite. +SYZOJ 2 uses [Sequelize](http://sequelizejs.com), which supports many database systems, including MySQL and Sqlite. But SYZOJ 2 supports **only** MySQL and Sqlite. By default it use the Sqlite database `syzoj.db`, you can change it in `config.json` diff --git a/app.js b/app.js index 33dc2b3..24bde8a 100644 --- a/app.js +++ b/app.js @@ -30,7 +30,7 @@ global.syzoj = { log(obj) { console.log(obj); }, - run() { + async run() { let Express = require('express'); global.app = Express(); @@ -60,11 +60,11 @@ global.syzoj = { let multer = require('multer'); app.multer = multer({ dest: syzoj.utils.resolvePath(syzoj.config.upload_dir, 'tmp') }); - this.connectDatabase(); + await this.connectDatabase(); this.loadHooks(); this.loadModules(); }, - connectDatabase() { + async connectDatabase() { let Sequelize = require('sequelize'); this.db = new Sequelize(this.config.db.database, this.config.db.username, this.config.db.password, { host: this.config.db.host, @@ -75,6 +75,15 @@ global.syzoj = { global.Promise = Sequelize.Promise; this.db.countQuery = async (sql, options) => (await this.db.query(`SELECT COUNT(*) FROM (${sql}) AS \`__tmp_table\``, options))[0][0]['COUNT(*)']; + if (this.config.db.dialect.toLowerCase() === 'mysql') { + await this.db.query('SET foreign_key_checks = 0;'); + } else if (this.config.db.dialect.toLowerCase() === 'sqlite') { + await this.db.query('PRAGMA foreign_keys = OFF;'); + } else { + this.log('Unsupported database: ' + this.config.db.dialect); + process.exit(); + } + this.loadModels(); }, loadModules() { diff --git a/models/article-comment.js b/models/article-comment.js index d996eeb..3a57fd8 100644 --- a/models/article-comment.js +++ b/models/article-comment.js @@ -30,21 +30,9 @@ let model = db.define('comment', { content: { type: Sequelize.TEXT }, - article_id: { - type: Sequelize.INTEGER, - references: { - model: 'article', - key: 'id' - } - }, + article_id: { type: Sequelize.INTEGER }, - user_id: { - type: Sequelize.INTEGER, - references: { - model: 'user', - key: 'id' - } - }, + user_id: { type: Sequelize.INTEGER }, public_time: { type: Sequelize.INTEGER } }, { diff --git a/models/article.js b/models/article.js index d3f9df1..898ce41 100644 --- a/models/article.js +++ b/models/article.js @@ -30,13 +30,7 @@ let model = db.define('article', { title: { type: Sequelize.STRING(80) }, content: { type: Sequelize.TEXT }, - user_id: { - type: Sequelize.INTEGER, - references: { - model: 'user', - key: 'id' - } - }, + user_id: { type: Sequelize.INTEGER }, public_time: { type: Sequelize.INTEGER }, update_time: { type: Sequelize.INTEGER }, diff --git a/models/common.js b/models/common.js index 5dfc3b2..45eafa9 100644 --- a/models/common.js +++ b/models/common.js @@ -83,6 +83,10 @@ class Model { return this.fromRecord(this.model.findOne(options)); } + static async all() { + return (await this.model.findAll()).mapAsync(record => (this.fromRecord(record))); + } + static async count(where) { // count(sql) if (typeof where === 'string') { diff --git a/models/contest.js b/models/contest.js index 21261ec..4d83542 100644 --- a/models/contest.js +++ b/models/contest.js @@ -92,10 +92,14 @@ class Contest extends Model { } async getProblems() { - if (this.problems === '') return []; + if (!this.problems) return []; return this.problems.split('|').map(x => parseInt(x)); } + async setProblemsNoCheck(problemIDs) { + this.problems = problemIDs.join('|'); + } + async setProblems(s) { let a = []; await s.split('|').forEachAsync(async x => { diff --git a/models/contest_player.js b/models/contest_player.js index 586e5ba..ea3ab79 100644 --- a/models/contest_player.js +++ b/models/contest_player.js @@ -28,20 +28,8 @@ 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' - } - }, + contest_id: { type: Sequelize.INTEGER }, + user_id: { type: Sequelize.INTEGER }, score: { type: Sequelize.INTEGER }, score_details: { type: Sequelize.TEXT, json: true }, diff --git a/models/judge_state.js b/models/judge_state.js index ef1fd74..4618fc5 100644 --- a/models/judge_state.js +++ b/models/judge_state.js @@ -39,21 +39,9 @@ let model = db.define('judge_state', { result: { type: Sequelize.TEXT('medium'), json: true }, - user_id: { - type: Sequelize.INTEGER, - references: { - model: User.model, - key: 'id' - } - }, + user_id: { type: Sequelize.INTEGER }, - problem_id: { - type: Sequelize.INTEGER, - references: { - model: Problem.model, - key: 'id' - } - }, + problem_id: { type: Sequelize.INTEGER }, submit_time: { type: Sequelize.INTEGER }, /* diff --git a/models/problem.js b/models/problem.js index 3e03b4e..5cbaea7 100644 --- a/models/problem.js +++ b/models/problem.js @@ -255,8 +255,9 @@ class Problem extends Model { let tmp = this.testdata_id; this.testdata_id = null; await this.save(); + let file = await TestData.fromID(tmp); - await file.destroy(); + if (file) await file.destroy(); } let filename = `test_data_${this.id}.zip`; @@ -425,6 +426,36 @@ class Problem extends Model { } } + async changeID(id) { + id = parseInt(id); + await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); + await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); + await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); + await db.query('UPDATE `file` SET `filename` = ' + `"test_data_${id}.zip"` + ' WHERE `filename` = ' + `"test_data_${this.id}.zip"`); + + let Contest = syzoj.model('contest'); + let contests = await Contest.all(); + for (let contest of contests) { + let problemIDs = await contest.getProblems(); + + let flag = false; + for (let i in problemIDs) { + if (problemIDs[i] === this.id) { + problemIDs[i] = id; + flag = true; + } + } + + if (flag) { + await contest.setProblemsNoCheck(problemIDs); + await contest.save(); + } + } + + this.id = id; + this.save(); + } + getModel() { return model; } } diff --git a/models/problem_tag_map.js b/models/problem_tag_map.js index 61c33bd..a49415f 100644 --- a/models/problem_tag_map.js +++ b/models/problem_tag_map.js @@ -26,11 +26,7 @@ let model = db.define('problem_tag_map', { problem_id: { type: Sequelize.INTEGER, primaryKey: true }, tag_id: { type: Sequelize.INTEGER, - primaryKey: true, - references: { - model: 'problem_tag', - key: 'id' - } + primaryKey: true } }, { timestamps: false, diff --git a/models/waiting_judge.js b/models/waiting_judge.js index 12f1049..2b95c7f 100644 --- a/models/waiting_judge.js +++ b/models/waiting_judge.js @@ -26,13 +26,7 @@ let JudgeState = syzoj.model('judge_state'); let model = db.define('waiting_judge', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - judge_id: { - type: Sequelize.INTEGER, - references: { - model: 'judge_state', - key: 'id' - } - } + judge_id: { type: Sequelize.INTEGER } }, { timestamps: false, tableName: 'waiting_judge', diff --git a/modules/problem.js b/modules/problem.js index d9a7147..bbdc621 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -204,6 +204,14 @@ app.post('/problem/:id/edit', async (req, res) => { } else { if (!await problem.isAllowedUseBy(res.locals.user)) throw 'Permission denied.'; if (!await problem.isAllowedEditBy(res.locals.user)) throw 'Permission denied.'; + + if (res.locals.user.is_admin) { + let customID = parseInt(req.body.id); + if (customID && customID !== id) { + if (await Problem.fromID(customID)) throw 'The ID has been used.'; + await problem.changeID(customID); + } + } } problem.title = req.body.title; diff --git a/views/problem_edit.ejs b/views/problem_edit.ejs index 30ce9e5..0b42b0d 100644 --- a/views/problem_edit.ejs +++ b/views/problem_edit.ejs @@ -13,9 +13,19 @@
- - > - + <% if (problem.new || user.is_admin) { %> + + + +
+ <% } %> +