diff --git a/.gitignore b/.gitignore index e912ab0..40879a0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ config.json *.db .git.* package-lock.json +models-built # Logs logs diff --git a/app.js b/app.js index 4c1740f..0db0646 100644 --- a/app.js +++ b/app.js @@ -12,6 +12,10 @@ const optionDefinitions = [ const options = commandLineArgs(optionDefinitions); +require('reflect-metadata'); + +global.Promise = require('bluebird'); + global.syzoj = { rootDir: __dirname, config: require('object-assign-deep')({}, require('./config-example.json'), require(options.config)), @@ -121,56 +125,36 @@ global.syzoj = { }); }, async connectDatabase() { - let Sequelize = require('sequelize'); - let Op = Sequelize.Op; - let operatorsAliases = { - $eq: Op.eq, - $ne: Op.ne, - $gte: Op.gte, - $gt: Op.gt, - $lte: Op.lte, - $lt: Op.lt, - $not: Op.not, - $in: Op.in, - $notIn: Op.notIn, - $is: Op.is, - $like: Op.like, - $notLike: Op.notLike, - $iLike: Op.iLike, - $notILike: Op.notILike, - $regexp: Op.regexp, - $notRegexp: Op.notRegexp, - $iRegexp: Op.iRegexp, - $notIRegexp: Op.notIRegexp, - $between: Op.between, - $notBetween: Op.notBetween, - $overlap: Op.overlap, - $contains: Op.contains, - $contained: Op.contained, - $adjacent: Op.adjacent, - $strictLeft: Op.strictLeft, - $strictRight: Op.strictRight, - $noExtendRight: Op.noExtendRight, - $noExtendLeft: Op.noExtendLeft, - $and: Op.and, - $or: Op.or, - $any: Op.any, - $all: Op.all, - $values: Op.values, - $col: Op.col + // Patch TypeORM to workaround https://github.com/typeorm/typeorm/issues/3636 + const TypeORMMysqlDriver = require('typeorm/driver/mysql/MysqlDriver'); + const OriginalNormalizeType = TypeORMMysqlDriver.MysqlDriver.prototype.normalizeType; + TypeORMMysqlDriver.MysqlDriver.prototype.normalizeType = function (column) { + if (column.type === 'json') { + return 'longtext'; + } + return OriginalNormalizeType(column); }; - this.db = new Sequelize(this.config.db.database, this.config.db.username, this.config.db.password, { - host: this.config.db.host, - dialect: 'mariadb', - logging: syzoj.production ? false : syzoj.log, - timezone: require('moment')().format('Z'), - operatorsAliases: operatorsAliases + const TypeORM = require('typeorm'); + global.TypeORM = TypeORM; + + const modelsPath = __dirname + '/models/'; + const modelsBuiltPath = __dirname + '/models-built/'; + const models = fs.readdirSync(modelsPath) + .filter(filename => filename.endsWith('.ts') && filename !== 'common.ts') + .map(filename => require(modelsBuiltPath + filename.replace('.ts', '.js')).default); + + await TypeORM.createConnection({ + type: 'mariadb', + host: this.config.db.host.split(':')[0], + port: this.config.db.host.split(':')[1] || 3306, + username: this.config.db.username, + password: this.config.db.password, + database: this.config.db.database, + entities: models, + synchronize: true, + logging: true }); - 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(*)']; - - await this.loadModels(); }, loadModules() { fs.readdir('./modules/', (err, files) => { @@ -182,22 +166,11 @@ global.syzoj = { .forEach((file) => this.modules.push(require(`./modules/${file}`))); }); }, - async loadModels() { - fs.readdir('./models/', (err, files) => { - if (err) { - this.log(err); - return; - } - files.filter((file) => file.endsWith('.js')) - .forEach((file) => require(`./models/${file}`)); - }); - await this.db.sync(); - }, lib(name) { return require(`./libs/${name}`); }, model(name) { - return require(`./models/${name}`); + return require(`./models-built/${name}`).default; }, loadHooks() { let Session = require('express-session'); @@ -221,7 +194,7 @@ global.syzoj = { let User = syzoj.model('user'); if (req.session.user_id) { - User.fromID(req.session.user_id).then((user) => { + User.findById(req.session.user_id).then((user) => { res.locals.user = user; next(); }).catch((err) => { diff --git a/libs/judger.js b/libs/judger.js index 93bec16..0963dd1 100644 --- a/libs/judger.js +++ b/libs/judger.js @@ -2,7 +2,7 @@ const enums = require('./enums'); const util = require('util'); const winston = require('winston'); const msgPack = require('msgpack-lite'); -const fs = Promise.promisifyAll(require('fs-extra')); +const fs = require('fs-extra'); const interface = require('./judger_interfaces'); const judgeResult = require('./judgeResult'); @@ -198,7 +198,7 @@ module.exports.judge = async function (judge_state, problem, priority) { case 'submit-answer': type = enums.ProblemType.AnswerSubmission; param = null; - extraData = await fs.readFileAsync(syzoj.model('file').resolvePath('answer', judge_state.code)); + extraData = await fs.readFile(syzoj.model('file').resolvePath('answer', judge_state.code)); break; case 'interaction': type = enums.ProblemType.Interaction; diff --git a/models/article-comment.js b/models/article-comment.js deleted file mode 100644 index b1b9028..0000000 --- a/models/article-comment.js +++ /dev/null @@ -1,56 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let User = syzoj.model('user'); -let Article = syzoj.model('article'); - -let model = db.define('comment', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - - content: { type: Sequelize.TEXT }, - - article_id: { type: Sequelize.INTEGER }, - - user_id: { type: Sequelize.INTEGER }, - - public_time: { type: Sequelize.INTEGER } -}, { - timestamps: false, - tableName: 'comment', - indexes: [ - { - fields: ['article_id'] - }, - { - fields: ['user_id'] - } - ] -}); - -let Model = require('./common'); -class ArticleComment extends Model { - static async create(val) { - return ArticleComment.fromRecord(ArticleComment.model.build(Object.assign({ - content: '', - article_id: 0, - user_id: 0, - public_time: 0, - }, val))); - } - - async loadRelationships() { - this.user = await User.fromID(this.user_id); - this.article = await Article.fromID(this.article_id); - } - - async isAllowedEditBy(user) { - await this.loadRelationships(); - return user && (user.is_admin || this.user_id === user.id || user.id === this.article.user_id); - } - - getModel() { return model; } -}; - -ArticleComment.model = model; - -module.exports = ArticleComment; diff --git a/models/article-comment.ts b/models/article-comment.ts new file mode 100644 index 0000000..ffff770 --- /dev/null +++ b/models/article-comment.ts @@ -0,0 +1,38 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +import User from "./user"; +import Article from "./article"; + +@TypeORM.Entity() +export default class ArticleComment extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Column({ nullable: true, type: "text" }) + content: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + article_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + public_time: number; + + user?: User; + article?: Article; + + async loadRelationships() { + this.user = await User.findById(this.user_id); + this.article = await Article.findById(this.article_id); + } + + async isAllowedEditBy(user) { + await this.loadRelationships(); + return user && (user.is_admin || this.user_id === user.id || user.id === this.article.user_id); + } +}; diff --git a/models/article.js b/models/article.js deleted file mode 100644 index d5e6b05..0000000 --- a/models/article.js +++ /dev/null @@ -1,91 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let User = syzoj.model('user'); -const Problem = syzoj.model('problem'); - -let model = db.define('article', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - - title: { type: Sequelize.STRING(80) }, - content: { type: Sequelize.TEXT('medium') }, - - user_id: { type: Sequelize.INTEGER }, - problem_id: { type: Sequelize.INTEGER }, - - public_time: { type: Sequelize.INTEGER }, - update_time: { type: Sequelize.INTEGER }, - sort_time: { type: Sequelize.INTEGER }, - - comments_num: { type: Sequelize.INTEGER }, - allow_comment: { type: Sequelize.BOOLEAN }, - - is_notice: { type: Sequelize.BOOLEAN } -}, { - timestamps: false, - tableName: 'article', - indexes: [ - { - fields: ['user_id'] - }, - { - fields: ['problem_id'] - }, - { - fields: ['sort_time'] - } - ] -}); - -let Model = require('./common'); -class Article extends Model { - static async create(val) { - return Article.fromRecord(Article.model.build(Object.assign({ - title: '', - content: '', - - user_id: 0, - problem_id: 0, - - public_time: 0, - update_time: 0, - sort_time: 0, - - comments_num: 0, - allow_comment: true, - - is_notice: false - }, val))); - } - - async loadRelationships() { - this.user = await User.fromID(this.user_id); - } - - async isAllowedEditBy(user) { - return user && (user.is_admin || this.user_id === user.id); - } - - async isAllowedCommentBy(user) { - return user && (this.allow_comment || user.is_admin || this.user_id === user.id); - } - - async resetReplyCountAndTime() { - let ArticleComment = syzoj.model('article-comment'); - await syzoj.utils.lock(['Article::resetReplyCountAndTime', this.id], async () => { - this.comments_num = await ArticleComment.count({ article_id: this.id }); - if (this.comments_num === 0) { - this.sort_time = this.public_time; - } else { - this.sort_time = await ArticleComment.model.max('public_time', { where: { article_id: this.id } }); - } - await this.save(); - }); - } - - getModel() { return model; } -}; - -Article.model = model; - -module.exports = Article; diff --git a/models/article.ts b/models/article.ts new file mode 100644 index 0000000..6445559 --- /dev/null +++ b/models/article.ts @@ -0,0 +1,78 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +import User from "./user"; +import Problem from "./problem"; +import ArticleComment from "./article-comment"; + +declare var syzoj: any; + +@TypeORM.Entity() +export default class Article extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + title: string; + + @TypeORM.Column({ nullable: true, type: "mediumtext" }) + content: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + problem_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + public_time: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + update_time: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + sort_time: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + comments_num: number; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + allow_comment: boolean; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_notice: boolean; + + user?: User; + problem?: Problem; + + async loadRelationships() { + this.user = await User.findById(this.user_id); + } + + async isAllowedEditBy(user) { + return user && (user.is_admin || this.user_id === user.id); + } + + async isAllowedCommentBy(user) { + return user && (this.allow_comment || user.is_admin || this.user_id === user.id); + } + + async resetReplyCountAndTime() { + await syzoj.utils.lock(['Article::resetReplyCountAndTime', this.id], async () => { + this.comments_num = await ArticleComment.count({ article_id: this.id }); + if (this.comments_num === 0) { + this.sort_time = this.public_time; + } else { + this.sort_time = (await ArticleComment.findOne({ + where: { article_id: this.id }, + order: { public_time: "DESC" } + })).public_time; + } + await this.save(); + }); + } +}; diff --git a/models/common.js b/models/common.js deleted file mode 100644 index 48e0c67..0000000 --- a/models/common.js +++ /dev/null @@ -1,136 +0,0 @@ -let Sequelize = require('sequelize'); - -class Model { - constructor(record) { - this.record = record; - this.loadFields(); - } - - loadFields() { - let model = this.getModel(); - let obj = JSON.parse(JSON.stringify(this.record.get({ plain: true }))); - for (let key in obj) { - if (!model.tableAttributes[key]) continue; - - if (model.tableAttributes[key].type instanceof Sequelize.JSON && typeof obj[key] === 'string') { - try { - this[key] = JSON.parse(obj[key]); - } catch (e) { - this[key] = {}; - } - } else this[key] = obj[key]; - } - } - - toPlain() { - let model = this.getModel(); - let obj = JSON.parse(JSON.stringify(this.record.get({ plain: true }))); - for (let key in obj) { - obj[key] = this[key]; - } - return obj; - } - - async save() { - let obj = this.toPlain(); - for (let key in obj) this.record.set(key, obj[key]); - - let isNew = this.record.isNewRecord; - - await syzoj.utils.withTimeoutRetry(() => this.record.save()); - if (!isNew) return; - - await this.reload(); - } - - async reload() { - await this.record.reload(); - this.loadFields(); - } - - async destroy() { - return this.record.destroy(); - } - - static async fromRecord(record) { - record = await record; - if (!record) return null; - return new this(await record); - } - - static async fromID(id) { - return this.fromRecord(this.model.findByPk(id)); - } - - static async findOne(options) { - 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') { - let sql = where; - return syzoj.db.countQuery(sql); - } - - // count(where) - return this.model.count({ where: where }); - } - - static async query(paginate, where, order, largeData) { - let records = []; - - if (typeof paginate === 'string') { - // query(sql) - let sql = paginate; - records = await syzoj.db.query(sql, { model: this.model }); - } else { - if (paginate && !Array.isArray(paginate) && !paginate.pageCnt) return []; - - 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 = parseInt(paginate.perPage); - } - - if (!largeData) records = await this.model.findAll(options); - else { - let sql = await getSqlFromFindAll(this.model, options); - let i = sql.indexOf('FROM'); - sql = 'SELECT id ' + sql.substr(i, sql.length - i); - sql = `SELECT a.* FROM (${sql}) AS b JOIN ${this.model.name} AS a ON a.id = b.id ORDER BY id DESC`; - records = await syzoj.db.query(sql, { model: this.model }); - } - } - - return records.mapAsync(record => (this.fromRecord(record))); - } -} - -function getSqlFromFindAll(Model, options) { - let id = require('uuid')(); - - return new Promise((resolve, reject) => { - Model.addHook('beforeFindAfterOptions', id, options => { - Model.removeHook('beforeFindAfterOptions', id); - - resolve(Model.sequelize.dialect.QueryGenerator.selectQuery(Model.getTableName(), options, Model).slice(0, -1)); - - return new Promise(() => {}); - }); - - return Model.findAll(options).catch(reject); - }); -} - -module.exports = Model; diff --git a/models/common.ts b/models/common.ts new file mode 100644 index 0000000..6b7e430 --- /dev/null +++ b/models/common.ts @@ -0,0 +1,63 @@ +import * as TypeORM from "typeorm"; + +interface Paginater { + pageCnt: number; + perPage: number; + currPage: number; +} + +export default class Model extends TypeORM.BaseEntity { + static async findById(this: TypeORM.ObjectType, id?: number): Promise { + return await (this as any).findOne(parseInt(id as any) || 0); + } + + async destroy() { + await TypeORM.getManager().remove(this); + } + + static async countQuery(this: TypeORM.ObjectType, query: TypeORM.SelectQueryBuilder | string) { + let parameters: any[] = null; + if (typeof query !== 'string') { + [query, parameters] = query.getQueryAndParameters(); + } + + return parseInt(( + await TypeORM.getManager().query(`SELECT COUNT(*) FROM (${query}) AS \`__tmp_table\``, parameters) + )[0]['COUNT(*)']); + } + + static async queryAll(queryBuilder) { + return await queryBuilder.getMany(); + } + + static async queryPage(paginater: Paginater, where, order) { + if (!paginater.pageCnt) return []; + + let queryBuilder = where instanceof TypeORM.SelectQueryBuilder + ? where + : this.createQueryBuilder().where(where); + + if (order) queryBuilder.orderBy(order); + + queryBuilder = queryBuilder.skip((paginater.currPage - 1) * paginater.perPage) + .take(paginater.perPage); + + return queryBuilder.getMany(); + } + + static async queryRange(range: any[], where, order) { + range[0] = parseInt(range[0]); + range[1] = parseInt(range[1]); + + let queryBuilder = where instanceof TypeORM.SelectQueryBuilder + ? where + : this.createQueryBuilder().where(where); + + if (order) queryBuilder.orderBy(order); + + queryBuilder = queryBuilder.skip(range[0] - 1) + .take(range[1] - range[0] + 1); + + return queryBuilder.getMany(); + } +} diff --git a/models/contest.js b/models/contest.ts similarity index 54% rename from models/contest.js rename to models/contest.ts index e5066e2..bce9905 100644 --- a/models/contest.js +++ b/models/contest.ts @@ -1,77 +1,69 @@ -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) }, - subtitle: { type: Sequelize.TEXT }, - start_time: { type: Sequelize.INTEGER }, - end_time: { type: Sequelize.INTEGER }, - - holder_id: { - type: Sequelize.INTEGER, - references: { - model: 'user', - key: 'id' - } - }, +import * as TypeORM from "typeorm"; +import Model from "./common"; + +declare var syzoj, ErrorMessage: any; + +import User from "./user"; +import Problem from "./problem"; +import ContestRanklist from "./contest_ranklist"; +import ContestPlayer from "./contest_player"; + +enum ContestType { + NOI = "noi", + IOI = "ioi", + ICPC = "acm" +} + +@TypeORM.Entity() +export default class Contest extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + title: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + subtitle: string; + + @TypeORM.Column({ nullable: true, type: "integer" }) + start_time: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + end_time: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + holder_id: number; + // type: noi, ioi, acm - type: { type: Sequelize.STRING(10) }, + @TypeORM.Column({ nullable: true, type: "enum", enum: ContestType }) + type: ContestType; - information: { type: Sequelize.TEXT }, - problems: { type: Sequelize.TEXT }, - admins: { type: Sequelize.TEXT }, + @TypeORM.Column({ nullable: true, type: "text" }) + information: string; - ranklist_id: { - type: Sequelize.INTEGER, - references: { - model: 'contest_ranklist', - key: 'id' - } - }, - - is_public: { type: Sequelize.BOOLEAN }, - hide_statistics: { type: Sequelize.BOOLEAN } -}, { - 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: '', - subtitle: '', - problems: '', - admins: '', - information: '', - type: 'noi', - start_time: 0, - end_time: 0, - holder: 0, - ranklist_id: 0, - is_public: false, - hide_statistics: false - }, val))); - } + @TypeORM.Column({ nullable: true, type: "text" }) + problems: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + admins: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + ranklist_id: number; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_public: boolean; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + hide_statistics: boolean; + + holder?: User; + ranklist?: ContestRanklist; async loadRelationships() { - this.holder = await User.fromID(this.holder_id); - this.ranklist = await ContestRanklist.fromID(this.ranklist_id); + this.holder = await User.findById(this.holder_id); + this.ranklist = await ContestRanklist.findById(this.ranklist_id); } async isSupervisior(user) { @@ -110,7 +102,7 @@ class Contest extends Model { async setProblems(s) { let a = []; await s.split('|').forEachAsync(async x => { - let problem = await Problem.fromID(x); + let problem = await Problem.findById(x); if (!problem) return; a.push(x); }); @@ -146,19 +138,13 @@ class Contest extends Model { }); } - isRunning(now) { + isRunning(now?) { if (!now) now = syzoj.utils.getCurrentDate(); return now >= this.start_time && now < this.end_time; } - isEnded(now) { + isEnded(now?) { if (!now) now = syzoj.utils.getCurrentDate(); return now >= this.end_time; } - - getModel() { return model; } } - -Contest.model = model; - -module.exports = Contest; diff --git a/models/contest_player.js b/models/contest_player.ts similarity index 72% rename from models/contest_player.js rename to models/contest_player.ts index 28a105d..dda7ddd 100644 --- a/models/contest_player.js +++ b/models/contest_player.ts @@ -1,50 +1,41 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let User = syzoj.model('user'); -let Problem = syzoj.model('problem'); - -let model = db.define('contest_player', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - contest_id: { type: Sequelize.INTEGER }, - user_id: { type: Sequelize.INTEGER }, - - score: { type: Sequelize.INTEGER }, - score_details: { type: Sequelize.JSON }, - 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))); - } +import * as TypeORM from "typeorm"; +import Model from "./common"; + +import User from "./user"; +import Contest from "./contest"; + +@TypeORM.Entity() +export default class ContestPlayer extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + contest_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + score: number; + + @TypeORM.Column({ nullable: true, type: "json" }) + score_details: object; + + @TypeORM.Column({ nullable: true, type: "integer" }) + time_spent: number; + + user?: User; + contest?: Contest; static async findInContest(where) { return ContestPlayer.findOne({ where: where }); } async loadRelationships() { - let Contest = syzoj.model('contest'); - this.user = await User.fromID(this.user_id); - this.contest = await Contest.fromID(this.contest_id); + this.user = await User.findById(this.user_id); + this.contest = await Contest.findById(this.contest_id); } async updateScore(judge_state) { @@ -65,7 +56,7 @@ class ContestPlayer extends Model { time: judge_state.submit_time }; - let arr = Object.values(this.score_details[judge_state.problem_id].submissions); + let arr: any = Object.values(this.score_details[judge_state.problem_id].submissions); arr.sort((a, b) => a.time - b.time); let maxScoreSubmission = null; @@ -117,7 +108,7 @@ class ContestPlayer extends Model { time: judge_state.submit_time }; - let arr = Object.values(this.score_details[judge_state.problem_id].submissions); + let arr: any = Object.values(this.score_details[judge_state.problem_id].submissions); arr.sort((a, b) => a.time - b.time); this.score_details[judge_state.problem_id].unacceptedCount = 0; @@ -145,10 +136,4 @@ class ContestPlayer extends Model { } } } - - getModel() { return model; } } - -ContestPlayer.model = model; - -module.exports = ContestPlayer; diff --git a/models/contest_ranklist.js b/models/contest_ranklist.ts similarity index 67% rename from models/contest_ranklist.js rename to models/contest_ranklist.ts index d082fbe..3bcc7ed 100644 --- a/models/contest_ranklist.js +++ b/models/contest_ranklist.ts @@ -1,32 +1,26 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; +import * as TypeORM from "typeorm"; +import Model from "./common"; -let User = syzoj.model('user'); -let Problem = syzoj.model('problem'); -let ContestPlayer = syzoj.model('contest_player'); +declare var syzoj: any; -let model = db.define('contest_ranklist', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - ranking_params: { type: Sequelize.JSON }, - ranklist: { type: Sequelize.JSON } -}, { - timestamps: false, - tableName: 'contest_ranklist' -}); +import ContestPlayer from "./contest_player"; +import JudgeState from "./judge_state"; -let Model = require('./common'); -class ContestRanklist extends Model { - static async create(val) { - return ContestRanklist.fromRecord(ContestRanklist.model.build(Object.assign({ - ranking_params: {}, - ranklist: {} - }, val))); - } +@TypeORM.Entity() +export default class ContestRanklist extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Column({ nullable: true, type: "json" }) + ranking_params: any; + + @TypeORM.Column({ nullable: true, type: "json" }) + ranklist: any; async getPlayers() { let a = []; for (let i = 1; i <= this.ranklist.player_num; i++) { - a.push(await ContestPlayer.fromID(this.ranklist[i])); + a.push(await ContestPlayer.findById(this.ranklist[i])); } return a; } @@ -44,15 +38,13 @@ class ContestRanklist extends Model { players.push(player); } - let JudgeState = syzoj.model('judge_state'); - if (contest.type === 'noi' || contest.type === 'ioi') { for (let player of players) { player.latest = 0; player.score = 0; for (let i in player.score_details) { - let judge_state = await JudgeState.fromID(player.score_details[i].judge_id); + let judge_state = await JudgeState.findById(player.score_details[i].judge_id); if (!judge_state) continue; player.latest = Math.max(player.latest, judge_state.submit_time); @@ -94,10 +86,4 @@ class ContestRanklist extends Model { 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; diff --git a/models/custom_test.js b/models/custom_test.js deleted file mode 100644 index 609541b..0000000 --- a/models/custom_test.js +++ /dev/null @@ -1,81 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let User = syzoj.model('user'); -let Problem = syzoj.model('problem'); - -let model = db.define('custom_test', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - - input_filepath: { type: Sequelize.TEXT }, - code: { type: Sequelize.TEXT('medium') }, - language: { type: Sequelize.STRING(20) }, - - status: { type: Sequelize.STRING(50) }, - - time: { type: Sequelize.INTEGER }, - pending: { type: Sequelize.BOOLEAN }, - memory: { type: Sequelize.INTEGER }, - - result: { type: Sequelize.JSON }, - - user_id: { type: Sequelize.INTEGER }, - - problem_id: { type: Sequelize.INTEGER }, - - submit_time: { type: Sequelize.INTEGER } -}, { - timestamps: false, - tableName: 'custom_test', - indexes: [ - { - fields: ['status'], - }, - { - fields: ['user_id'], - }, - { - fields: ['problem_id'], - } - ] -}); - -let Model = require('./common'); -class CustomTest extends Model { - static async create(val) { - return CustomTest.fromRecord(CustomTest.model.build(Object.assign({ - input_filepath: '', - code: '', - language: '', - user_id: 0, - problem_id: 0, - submit_time: parseInt((new Date()).getTime() / 1000), - - pending: true, - - time: 0, - memory: 0, - result: {}, - status: 'Waiting', - }, val))); - } - - async loadRelationships() { - this.user = await User.fromID(this.user_id); - this.problem = await Problem.fromID(this.problem_id); - } - - async updateResult(result) { - this.pending = result.pending; - this.status = result.status; - this.time = result.time_used; - this.memory = result.memory_used; - this.result = result; - } - - getModel() { return model; } -} - -CustomTest.model = model; - -module.exports = CustomTest; diff --git a/models/file.js b/models/file.ts similarity index 60% rename from models/file.js rename to models/file.ts index 1308f52..95a0e0a 100644 --- a/models/file.js +++ b/models/file.ts @@ -1,31 +1,24 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('file', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - type: { type: Sequelize.STRING(80) }, - md5: { type: Sequelize.STRING(80), unique: true } -}, { - timestamps: false, - tableName: 'file', - indexes: [ - { - fields: ['type'], - }, - { - fields: ['md5'], - } - ] -}); - -let Model = require('./common'); -class File extends Model { - static create(val) { - return File.fromRecord(File.model.build(Object.assign({ - type: '', - md5: '' - }, val))); - } +import * as TypeORM from "typeorm"; +import Model from "./common"; + +import * as fs from "fs-extra"; + +declare var syzoj, ErrorMessage: any; + +@TypeORM.Entity() +export default class File extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + type: string; + + @TypeORM.Index({ unique: true }) + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + md5: string; + + unzipSize?: number; getPath() { return File.resolvePath(this.type, this.md5); @@ -54,24 +47,12 @@ class File extends Model { } static async upload(path, type, noLimit) { - let fs = Promise.promisifyAll(require('fs-extra')); - - let buf = await fs.readFileAsync(path); + let buf = await fs.readFile(path); if (!noLimit && buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。'); - try { - let p7zip = new (require('node-7z')); - this.unzipSize = 0; - await p7zip.list(path).progress(files => { - for (let file of files) this.unzipSize += file.size; - }); - } catch (e) { - this.unzipSize = null; - } - let key = syzoj.utils.md5(buf); - await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true }); + await fs.move(path, File.resolvePath(type, key), { overwrite: true }); let file = await File.findOne({ where: { md5: key } }); if (!file) { @@ -101,10 +82,4 @@ class File extends Model { if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。'); else return this.unzipSize; } - - getModel() { return model; } } - -File.model = model; - -module.exports = File; diff --git a/models/formatted_code.js b/models/formatted_code.js deleted file mode 100644 index 4ff3239..0000000 --- a/models/formatted_code.js +++ /dev/null @@ -1,31 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('formatted_code', { - key: { type: Sequelize.STRING(50), primaryKey: true }, - code: { type: Sequelize.TEXT('medium') } -}, { - timestamps: false, - tableName: 'formatted_code', - indexes: [ - { - fields: ['key'] - } - ] -}); - -let Model = require('./common'); -class FormattedCode extends Model { - static async create(val) { - return FormattedCode.fromRecord(FormattedCode.model.build(Object.assign({ - key: "", - code: "" - }, val))); - } - - getModel() { return model; } -} - -FormattedCode.model = model; - -module.exports = FormattedCode; diff --git a/models/formatted_code.ts b/models/formatted_code.ts new file mode 100644 index 0000000..3de8735 --- /dev/null +++ b/models/formatted_code.ts @@ -0,0 +1,11 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +@TypeORM.Entity() +export default class FormattedCode extends Model { + @TypeORM.Column({ type: "varchar", length: 50, primary: true }) + key: string; + + @TypeORM.Column({ nullable: true, type: "mediumtext" }) + code: string; +} diff --git a/models/judge_state.js b/models/judge_state.js deleted file mode 100644 index f937c2a..0000000 --- a/models/judge_state.js +++ /dev/null @@ -1,195 +0,0 @@ -let Sequelize = require('sequelize'); -const randomstring = require('randomstring'); -let db = syzoj.db; - -let User = syzoj.model('user'); -let Problem = syzoj.model('problem'); -let Contest = syzoj.model('contest'); - -let Judger = syzoj.lib('judger'); - -let model = db.define('judge_state', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - - // The data zip's md5 if it's a submit-answer problem - code: { type: Sequelize.TEXT('medium') }, - language: { type: Sequelize.STRING(20) }, - - status: { type: Sequelize.STRING(50) }, - task_id: { type: Sequelize.STRING(50) }, - score: { type: Sequelize.INTEGER }, - total_time: { type: Sequelize.INTEGER }, - code_length: { type: Sequelize.INTEGER }, - pending: { type: Sequelize.BOOLEAN }, - max_memory: { type: Sequelize.INTEGER }, - - // For NOI contest - compilation: { type: Sequelize.JSON }, - - result: { type: Sequelize.JSON }, - - user_id: { type: Sequelize.INTEGER }, - - problem_id: { type: Sequelize.INTEGER }, - - submit_time: { type: Sequelize.INTEGER }, - /* - * "type" indicate it's contest's submission(type = 1) or normal submission(type = 0) - * if it's contest's submission (type = 1), the type_info is contest_id - * use this way represent because it's easy to expand // Menci:这锅我不背,是 Chenyao 留下来的坑。 - */ - type: { type: Sequelize.INTEGER }, - type_info: { type: Sequelize.INTEGER }, - is_public: { type: Sequelize.BOOLEAN } -}, { - timestamps: false, - tableName: 'judge_state', - indexes: [ - { - fields: ['status'], - }, - { - fields: ['score'], - }, - { - fields: ['user_id'], - }, - { - fields: ['problem_id'], - }, - { - fields: ['task_id'], - }, - { - fields: ['id', 'is_public', 'type_info', 'type'] - } - ] - }); - -let Model = require('./common'); -class JudgeState extends Model { - static async create(val) { - return JudgeState.fromRecord(JudgeState.model.build(Object.assign({ - code: '', - code_length: 0, - language: null, - user_id: 0, - problem_id: 0, - submit_time: parseInt((new Date()).getTime() / 1000), - - type: 0, - type_info: 0, - - pending: false, - - score: null, - total_time: null, - max_memory: null, - status: 'Unknown', - compilation: {}, - result: {}, - task_id: randomstring.generate(10), - is_public: false - }, val))); - } - - async loadRelationships() { - if (!this.user) { - this.user = await User.fromID(this.user_id); - } - if (!this.problem) { - if (this.problem_id) this.problem = await Problem.fromID(this.problem_id); - } - } - - async isAllowedVisitBy(user) { - await this.loadRelationships(); - - if (user && user.id === this.problem.user_id) return true; - else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem'))); - else if (this.type === 1) { - let contest = await Contest.fromID(this.type_info); - if (contest.isRunning()) { - return user && await contest.isSupervisior(user); - } else { - return true; - } - } - } - - async updateRelatedInfo(newSubmission) { - if (this.type === 0) { - await this.loadRelationships(); - - // No need to await them. - this.user.refreshSubmitInfo(); - this.problem.resetSubmissionCount(); - } else if (this.type === 1) { - let contest = await Contest.fromID(this.type_info); - await contest.newSubmission(this); - } - } - - async rejudge() { - await syzoj.utils.lock(['JudgeState::rejudge', this.id], async () => { - await this.loadRelationships(); - - let oldStatus = this.status; - - this.status = 'Unknown'; - this.pending = false; - this.score = null; - if (this.language) { - // language is empty if it's a submit-answer problem - this.total_time = null; - this.max_memory = null; - } - this.result = {}; - this.task_id = randomstring.generate(10); - await this.save(); - - /* - let WaitingJudge = syzoj.model('waiting_judge'); - let waiting_judge = await WaitingJudge.create({ - judge_id: this.id, - priority: 2, - type: 'submission' - }); - - await waiting_judge.save(); - */ - - await this.problem.resetSubmissionCount(); - if (oldStatus === 'Accepted') { - await this.user.refreshSubmitInfo(); - await this.user.save(); - } - - if (this.type === 1) { - let contest = await Contest.fromID(this.type_info); - await contest.newSubmission(this); - } - - try { - await Judger.judge(this, this.problem, 1); - this.pending = true; - this.status = 'Waiting'; - await this.save(); - } catch (err) { - console.log("Error while connecting to judge frontend: " + err.toString()); - throw new ErrorMessage("无法开始评测。"); - } - }); - } - - async getProblemType() { - await this.loadRelationships(); - return this.problem.type; - } - - getModel() { return model; } -} - -JudgeState.model = model; - -module.exports = JudgeState; diff --git a/models/judge_state.ts b/models/judge_state.ts new file mode 100644 index 0000000..a3673c9 --- /dev/null +++ b/models/judge_state.ts @@ -0,0 +1,165 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +declare var syzoj, ErrorMessage: any; + +import User from "./user"; +import Problem from "./problem"; +import Contest from "./contest"; + +const Judger = syzoj.lib('judger'); + +@TypeORM.Entity() +@TypeORM.Index(['id', 'is_public', 'type_info', 'type']) +export default class JudgeState extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + // The data zip's md5 if it's a submit-answer problem + @TypeORM.Column({ nullable: true, type: "mediumtext" }) + code: string; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 20 }) + language: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "varchar", length: 50 }) + status: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "varchar", length: 50 }) + task_id: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + score: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + total_time: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + code_length: number; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + pending: boolean; + + @TypeORM.Column({ nullable: true, type: "integer" }) + max_memory: number; + + @TypeORM.Column({ nullable: true, type: "json" }) + compilation: any; + + @TypeORM.Column({ nullable: true, type: "json" }) + result: any; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + problem_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + submit_time: number; + + /* + * "type" indicate it's contest's submission(type = 1) or normal submission(type = 0) + * if it's contest's submission (type = 1), the type_info is contest_id + * use this way represent because it's easy to expand // Menci:这锅我不背,是 Chenyao 留下来的坑。 + */ + @TypeORM.Column({ nullable: true, type: "integer" }) + type: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + type_info: number; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_public: boolean; + + user?: User; + problem?: Problem; + + async loadRelationships() { + if (!this.user) { + this.user = await User.findById(this.user_id); + } + if (!this.problem) { + if (this.problem_id) this.problem = await Problem.findById(this.problem_id); + } + } + + async isAllowedVisitBy(user) { + await this.loadRelationships(); + + if (user && user.id === this.problem.user_id) return true; + else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem'))); + else if (this.type === 1) { + let contest = await Contest.findById(this.type_info); + if (contest.isRunning()) { + return user && await contest.isSupervisior(user); + } else { + return true; + } + } + } + + async updateRelatedInfo(newSubmission) { + if (this.type === 0) { + await this.loadRelationships(); + + // No need to await them. + this.user.refreshSubmitInfo(); + this.problem.resetSubmissionCount(); + } else if (this.type === 1) { + let contest = await Contest.findById(this.type_info); + await contest.newSubmission(this); + } + } + + async rejudge() { + await syzoj.utils.lock(['JudgeState::rejudge', this.id], async () => { + await this.loadRelationships(); + + let oldStatus = this.status; + + this.status = 'Unknown'; + this.pending = false; + this.score = null; + if (this.language) { + // language is empty if it's a submit-answer problem + this.total_time = null; + this.max_memory = null; + } + this.result = {}; + this.task_id = require('randomstring').generate(10); + await this.save(); + + await this.problem.resetSubmissionCount(); + if (oldStatus === 'Accepted') { + await this.user.refreshSubmitInfo(); + await this.user.save(); + } + + if (this.type === 1) { + let contest = await Contest.findById(this.type_info); + await contest.newSubmission(this); + } + + try { + await Judger.judge(this, this.problem, 1); + this.pending = true; + this.status = 'Waiting'; + await this.save(); + } catch (err) { + console.log("Error while connecting to judge frontend: " + err.toString()); + throw new ErrorMessage("无法开始评测。"); + } + }); + } + + async getProblemType() { + await this.loadRelationships(); + return this.problem.type; + } +} diff --git a/models/problem.js b/models/problem.ts similarity index 74% rename from models/problem.js rename to models/problem.ts index 822a1cb..5ac43e9 100644 --- a/models/problem.js +++ b/models/problem.ts @@ -1,4 +1,4 @@ -let statisticsStatements = { +const statisticsStatements = { fastest: '\ SELECT \ @@ -176,109 +176,108 @@ ORDER BY `max_memory` DESC \ ' }; -let Sequelize = require('sequelize'); -let db = syzoj.db; +import * as TypeORM from "typeorm"; +import Model from "./common"; -let User = syzoj.model('user'); -let File = syzoj.model('file'); -const fs = require('fs-extra'); -const path = require('path'); +declare var syzoj, ErrorMessage: any; -let model = db.define('problem', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, +import User from "./user"; +import File from "./file"; +import JudgeState from "./judge_state"; +import Contest from "./contest"; +import ProblemTag from "./problem_tag"; +import ProblemTagMap from "./problem_tag_map"; - title: { type: Sequelize.STRING(80) }, - user_id: { - type: Sequelize.INTEGER, - references: { - model: 'user', - key: 'id' - } - }, - publicizer_id: { - type: Sequelize.INTEGER, - references: { - model: 'user', - key: 'id' - } - }, - is_anonymous: { type: Sequelize.BOOLEAN }, +import * as fs from "fs-extra"; +import * as path from "path"; +import * as util from "util"; - description: { type: Sequelize.TEXT }, - input_format: { type: Sequelize.TEXT }, - output_format: { type: Sequelize.TEXT }, - example: { type: Sequelize.TEXT }, - limit_and_hint: { type: Sequelize.TEXT }, +enum ProblemType { + Traditional = "traditional", + SubmitAnswer = "submit-answer", + Interaction = "interaction" +} - time_limit: { type: Sequelize.INTEGER }, - memory_limit: { type: Sequelize.INTEGER }, +@TypeORM.Entity() +export default class Problem extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; - additional_file_id: { type: Sequelize.INTEGER }, + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + title: string; - ac_num: { type: Sequelize.INTEGER }, - submit_num: { type: Sequelize.INTEGER }, - is_public: { type: Sequelize.BOOLEAN }, + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + user_id: number; - file_io: { type: Sequelize.BOOLEAN }, - file_io_input_name: { type: Sequelize.TEXT }, - file_io_output_name: { type: Sequelize.TEXT }, + @TypeORM.Column({ nullable: true, type: "integer" }) + publicizer_id: number; - publicize_time: { type: Sequelize.DATE }, + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_anonymous: boolean; - type: { - type: Sequelize.ENUM, - values: ['traditional', 'submit-answer', 'interaction'] - } -}, { - timestamps: false, - tableName: 'problem', - indexes: [ - { - fields: ['title'], - }, - { - fields: ['user_id'], - }, - { - fields: ['publicize_time'], - }, - ] - }); - -let Model = require('./common'); -class Problem extends Model { - static async create(val) { - return Problem.fromRecord(Problem.model.build(Object.assign({ - title: '', - user_id: '', - publicizer_id: '', - is_anonymous: false, - description: '', - - input_format: '', - output_format: '', - example: '', - limit_and_hint: '', - - time_limit: syzoj.config.default.problem.time_limit, - memory_limit: syzoj.config.default.problem.memory_limit, - - ac_num: 0, - submit_num: 0, - is_public: false, - - file_io: false, - file_io_input_name: '', - file_io_output_name: '', - - type: 'traditional' - }, val))); - } + @TypeORM.Column({ nullable: true, type: "text" }) + description: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + input_format: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + output_format: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + example: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + limit_and_hint: string; + + @TypeORM.Column({ nullable: true, type: "integer" }) + time_limit: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + memory_limit: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + additional_file_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + ac_num: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + submit_num: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_public: boolean; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + file_io: boolean; + + @TypeORM.Column({ nullable: true, type: "text" }) + file_io_input_name: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + file_io_output_name: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "datetime" }) + publicize_time: Date; + + @TypeORM.Column({ nullable: true, + type: "enum", + enum: ProblemType, + default: ProblemType.Traditional + }) + type: ProblemType; + + user?: User; + publicizer?: User; + additional_file?: File; async loadRelationships() { - this.user = await User.fromID(this.user_id); - this.publicizer = await User.fromID(this.publicizer_id); - this.additional_file = await File.fromID(this.additional_file_id); + this.user = await User.findById(this.user_id); + this.publicizer = await User.findById(this.publicizer_id); + this.additional_file = await File.findById(this.additional_file_id); } async isAllowedEditBy(user) { @@ -324,7 +323,7 @@ class Problem extends Model { await fs.remove(dir); await fs.ensureDir(dir); - let execFileAsync = Promise.promisify(require('child_process').execFile); + let execFileAsync = util.promisify(require('child_process').execFile); await execFileAsync(__dirname + '/../bin/unzip', ['-j', '-o', '-d', dir, path]); await fs.move(path, this.getTestdataArchivePath(), { overwrite: true }); }); @@ -345,11 +344,11 @@ class Problem extends Model { } if (!noLimit && oldSize + size > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。'); - if (!noLimit && oldCount + !replace > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。'); + if (!noLimit && oldCount + (!replace as any as number) > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。'); await fs.move(filepath, path.join(dir, filename), { overwrite: true }); - let execFileAsync = Promise.promisify(require('child_process').execFile); + let execFileAsync = util.promisify(require('child_process').execFile); try { await execFileAsync('dos2unix', [path.join(dir, filename)]); } catch (e) {} await fs.remove(this.getTestdataArchivePath()); @@ -390,15 +389,15 @@ class Problem extends Model { async listTestdata() { try { let dir = this.getTestdataPath(); - let list = await fs.readdir(dir); - list = await list.mapAsync(async x => { + let filenameList = await fs.readdir(dir); + let list = await Promise.all(filenameList.map(async x => { let stat = await fs.stat(path.join(dir, x)); if (!stat.isFile()) return undefined; return { filename: x, size: stat.size }; - }); + })); list = list.filter(x => x); @@ -461,9 +460,8 @@ class Problem extends Model { async getJudgeState(user, acFirst) { if (!user) return null; - let JudgeState = syzoj.model('judge_state'); - let where = { + let where: any = { user_id: user.id, problem_id: this.id }; @@ -473,7 +471,9 @@ class Problem extends Model { let state = await JudgeState.findOne({ where: where, - order: [['submit_time', 'desc']] + order: { + submit_time: 'DESC' + } }); if (state) return state; @@ -483,15 +483,16 @@ class Problem extends Model { return await JudgeState.findOne({ where: where, - order: [['submit_time', 'desc']] + order: { + submit_time: 'DESC' + } }); } async resetSubmissionCount() { - let JudgeState = syzoj.model('judge_state'); await syzoj.utils.lock(['Problem::resetSubmissionCount', this.id], async () => { - this.submit_num = await JudgeState.count({ problem_id: this.id, type: { $not: 1 } }); - this.ac_num = await JudgeState.count({ score: 100, problem_id: this.id, type: { $not: 1 } }); + this.submit_num = await JudgeState.count({ problem_id: this.id, type: TypeORM.Not(1) }); + this.ac_num = await JudgeState.count({ score: 100, problem_id: this.id, type: TypeORM.Not(1) }); await this.save(); }); } @@ -501,12 +502,16 @@ class Problem extends Model { let statement = statisticsStatements[type]; if (!statement) return null; + const entityManager = TypeORM.getManager(); + statement = statement.replace('__PROBLEM_ID__', this.id); - return await db.countQuery(statement); + return JudgeState.countQuery(statement); } // type: fastest / slowest / shortest / longest / earliest async getStatistics(type, paginate) { + const entityManager = TypeORM.getManager(); + let statistics = { type: type, judge_state: null, @@ -522,12 +527,11 @@ class Problem extends Model { let a; if (!paginate.pageCnt) a = []; - else a = (await db.query(statement + `LIMIT ${paginate.perPage} OFFSET ${(paginate.currPage - 1) * paginate.perPage}`))[0]; + else a = (await entityManager.query(statement + `LIMIT ${paginate.perPage} OFFSET ${(paginate.currPage - 1) * paginate.perPage}`))[0]; - let JudgeState = syzoj.model('judge_state'); - statistics.judge_state = await a.mapAsync(async x => JudgeState.fromID(x.id)); + statistics.judge_state = await a.mapAsync(async x => JudgeState.findById(x.id)); - a = (await db.query('SELECT `score`, COUNT(*) AS `count` FROM `judge_state` WHERE `problem_id` = __PROBLEM_ID__ AND `type` = 0 AND `pending` = 0 GROUP BY `score`'.replace('__PROBLEM_ID__', this.id)))[0]; + a = (await entityManager.query('SELECT `score`, COUNT(*) AS `count` FROM `judge_state` WHERE `problem_id` = __PROBLEM_ID__ AND `type` = 0 AND `pending` = 0 GROUP BY `score`'.replace('__PROBLEM_ID__', this.id.toString())))[0]; let scoreCount = []; for (let score of a) { @@ -557,14 +561,14 @@ class Problem extends Model { } async getTags() { - let ProblemTagMap = syzoj.model('problem_tag_map'); - let maps = await ProblemTagMap.query(null, { - problem_id: this.id + let maps = await ProblemTagMap.find({ + where: { + problem_id: this.id + } }); - let ProblemTag = syzoj.model('problem_tag'); - let res = await maps.mapAsync(async map => { - return ProblemTag.fromID(map.tag_id); + let res = await (maps as any).mapAsync(async map => { + return ProblemTag.findById(map.tag_id); }); res.sort((a, b) => { @@ -575,8 +579,6 @@ class Problem extends Model { } 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)); @@ -604,14 +606,16 @@ class Problem extends Model { } async changeID(id) { + const entityManager = TypeORM.getManager(); + 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 `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); + await entityManager.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); + await entityManager.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); + await entityManager.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); + await entityManager.query('UPDATE `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); - let Contest = syzoj.model('contest'); - let contests = await Contest.all(); + + let contests = await Contest.find(); for (let contest of contests) { let problemIDs = await contest.getProblems(); @@ -647,12 +651,17 @@ class Problem extends Model { } async delete() { + const entityManager = TypeORM.getManager(); + let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath(); await fs.remove(oldTestdataDir); await fs.remove(oldTestdataZip); - let JudgeState = syzoj.model('judge_state'); - let submissions = await JudgeState.query(null, { problem_id: this.id }), submitCnt = {}, acUsers = new Set(); + let submissions = await JudgeState.find({ + where: { + problem_id: this.id + } + }), submitCnt = {}, acUsers = new Set(); for (let sm of submissions) { if (sm.status === 'Accepted') acUsers.add(sm.user_id); if (!submitCnt[sm.user_id]) { @@ -663,21 +672,15 @@ class Problem extends Model { } for (let u in submitCnt) { - let user = await User.fromID(u); + let user = await User.findById(parseInt(u)); user.submit_num -= submitCnt[u]; if (acUsers.has(parseInt(u))) user.ac_num--; await user.save(); } - await db.query('DELETE FROM `problem` WHERE `id` = ' + this.id); - await db.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id); - await db.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id); - await db.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id); + await entityManager.query('DELETE FROM `problem` WHERE `id` = ' + this.id); + await entityManager.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id); + await entityManager.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id); + await entityManager.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id); } - - getModel() { return model; } } - -Problem.model = model; - -module.exports = Problem; diff --git a/models/problem_tag.js b/models/problem_tag.js deleted file mode 100644 index a354ecb..0000000 --- a/models/problem_tag.js +++ /dev/null @@ -1,33 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('problem_tag', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - name: { type: Sequelize.STRING }, - color: { type: Sequelize.STRING }, -}, { - timestamps: false, - tableName: 'problem_tag', - indexes: [ - { - unique: true, - fields: ['name'], - } - ] -}); - -let Model = require('./common'); -class ProblemTag extends Model { - static async create(val) { - return ProblemTag.fromRecord(ProblemTag.model.build(Object.assign({ - name: '', - color: '' - }, val))); - } - - getModel() { return model; } -} - -ProblemTag.model = model; - -module.exports = ProblemTag; diff --git a/models/problem_tag.ts b/models/problem_tag.ts new file mode 100644 index 0000000..ca059b8 --- /dev/null +++ b/models/problem_tag.ts @@ -0,0 +1,15 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +@TypeORM.Entity() +export default class ProblemTag extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Index({ unique: true }) + @TypeORM.Column({ nullable: true, type: "varchar", length: 255 }) + name: string; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 255 }) + color: string; +} diff --git a/models/problem_tag_map.js b/models/problem_tag_map.js deleted file mode 100644 index 3fcee39..0000000 --- a/models/problem_tag_map.js +++ /dev/null @@ -1,37 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('problem_tag_map', { - problem_id: { type: Sequelize.INTEGER, primaryKey: true }, - tag_id: { - type: Sequelize.INTEGER, - primaryKey: true - } -}, { - timestamps: false, - tableName: 'problem_tag_map', - indexes: [ - { - fields: ['problem_id'] - }, - { - fields: ['tag_id'] - } - ] -}); - -let Model = require('./common'); -class ProblemTagMap extends Model { - static async create(val) { - return ProblemTagMap.fromRecord(ProblemTagMap.model.build(Object.assign({ - problem_id: 0, - tag_id: 0 - }, val))); - } - - getModel() { return model; } -} - -ProblemTagMap.model = model; - -module.exports = ProblemTagMap; diff --git a/models/problem_tag_map.ts b/models/problem_tag_map.ts new file mode 100644 index 0000000..f7f710b --- /dev/null +++ b/models/problem_tag_map.ts @@ -0,0 +1,13 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +@TypeORM.Entity() +export default class ProblemTagMap extends Model { + @TypeORM.Index() + @TypeORM.Column({ type: "integer", primary: true }) + problem_id: number; + + @TypeORM.Index() + @TypeORM.Column({ type: "integer", primary: true }) + tag_id: number; +} diff --git a/models/rating_calculation.js b/models/rating_calculation.js deleted file mode 100644 index 31cfea6..0000000 --- a/models/rating_calculation.js +++ /dev/null @@ -1,53 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; -const User = syzoj.model('user'); -const Contest = syzoj.model('contest'); - -let model = db.define('rating_calculation', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - contest_id: { type: Sequelize.INTEGER } -}, { - timestamps: false, - tableName: 'rating_calculation', - indexes: [ - { - fields: ['contest_id'] - }, - ] - }); - -let Model = require('./common'); -class RatingCalculation extends Model { - static async create(contest_id) { - return RatingCalculation.fromRecord(RatingCalculation.model.create({ contest_id: contest_id })); - } - - async loadRelationships() { - this.contest = await Contest.fromID(this.contest_id); - } - - getModel() { return model; } - - async delete() { - const RatingHistory = syzoj.model('rating_history'); - const histories = await RatingHistory.query(null, { - rating_calculation_id: this.id - }); - for (const history of histories) { - await history.loadRelationships(); - const user = history.user; - await history.destroy(); - const ratingItem = (await RatingHistory.findOne({ - where: { user_id: user.id }, - order: [['rating_calculation_id','DESC']] - })); - user.rating = ratingItem ? ratingItem.rating_after : syzoj.config.default.user.rating; - await user.save(); - } - await this.destroy(); - } -} - -RatingCalculation.model = model; - -module.exports = RatingCalculation; diff --git a/models/rating_calculation.ts b/models/rating_calculation.ts new file mode 100644 index 0000000..0b10fce --- /dev/null +++ b/models/rating_calculation.ts @@ -0,0 +1,47 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +declare var syzoj: any; + +import Contest from "./contest"; +import RatingHistory from "./rating_history"; + +@TypeORM.Entity() +export default class RatingCalculation extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Index({}) + @TypeORM.Column({ nullable: true, type: "integer" }) + contest_id: number; + + contest?: Contest; + + async loadRelationships() { + this.contest = await Contest.findById(this.contest_id); + } + + async delete() { + const histories = await RatingHistory.find({ + where: { + rating_calculation_id: this.id + } + }); + for (const history of histories) { + await history.loadRelationships(); + const user = history.user; + await history.destroy(); + const ratingItem = (await RatingHistory.findOne({ + where: { + user_id: user.id + }, + order: { + rating_calculation_id: 'DESC' + } + })); + user.rating = ratingItem ? ratingItem.rating_after : syzoj.config.default.user.rating; + await user.save(); + } + await this.destroy(); + } +} diff --git a/models/rating_history.js b/models/rating_history.js deleted file mode 100644 index fa06270..0000000 --- a/models/rating_history.js +++ /dev/null @@ -1,43 +0,0 @@ -let Sequelize = require('sequelize'); -const User = syzoj.model('user'); -let db = syzoj.db; - -let model = db.define('rating_history', { - rating_calculation_id: { type: Sequelize.INTEGER, primaryKey: true }, - user_id: { type: Sequelize.INTEGER, primaryKey: true }, - rating_after: { type: Sequelize.INTEGER }, - rank: { type: Sequelize.INTEGER }, -}, { - timestamps: false, - tableName: 'rating_history', - indexes: [ - { - fields: ['rating_calculation_id'] - }, - { - fields: ['user_id'] - }, - ] - }); - -let Model = require('./common'); -class RatingHistory extends Model { - static async create(rating_calculation_id, user_id, rating, rank) { - return RatingHistory.fromRecord(RatingHistory.model.build({ - rating_calculation_id: rating_calculation_id, - user_id: user_id, - rating_after: rating, - rank: rank - })); - } - - async loadRelationships() { - this.user = await User.fromID(this.user_id); - } - - getModel() { return model; } -} - -RatingHistory.model = model; - -module.exports = RatingHistory; diff --git a/models/rating_history.ts b/models/rating_history.ts new file mode 100644 index 0000000..86161f7 --- /dev/null +++ b/models/rating_history.ts @@ -0,0 +1,27 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +declare var syzoj: any; + +import User from "./user"; + +@TypeORM.Entity() +export default class RatingHistory extends Model { + @TypeORM.Column({ type: "integer", primary: true }) + rating_calculation_id: number; + + @TypeORM.Column({ type: "integer", primary: true }) + user_id: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + rating_after: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + rank: number; + + user: User; + + async loadRelationships() { + this.user = await User.findById(this.user_id); + } +} diff --git a/models/user.js b/models/user.js deleted file mode 100644 index d8db557..0000000 --- a/models/user.js +++ /dev/null @@ -1,235 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('user', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - username: { type: Sequelize.STRING(80), unique: true }, - email: { type: Sequelize.STRING(120) }, - password: { type: Sequelize.STRING(120) }, - - nickname: { type: Sequelize.STRING(80) }, - nameplate: { type: Sequelize.TEXT }, - information: { type: Sequelize.TEXT }, - - ac_num: { type: Sequelize.INTEGER }, - submit_num: { type: Sequelize.INTEGER }, - - is_admin: { type: Sequelize.BOOLEAN }, - is_show: { type: Sequelize.BOOLEAN }, - public_email: { type: Sequelize.BOOLEAN }, - prefer_formatted_code: { type: Sequelize.BOOLEAN }, - - sex: { type: Sequelize.INTEGER }, - rating: { type: Sequelize.INTEGER }, - - register_time: { type: Sequelize.INTEGER } -}, { - timestamps: false, - tableName: 'user', - indexes: [ - { - fields: ['username'], - unique: true - }, - { - fields: ['nickname'], - }, - { - fields: ['ac_num'], - } - ] -}); - -let Model = require('./common'); -class User extends Model { - static async create(val) { - return User.fromRecord(User.model.build(Object.assign({ - username: '', - password: '', - email: '', - - nickname: '', - is_admin: false, - ac_num: 0, - submit_num: 0, - sex: 0, - is_show: syzoj.config.default.user.show, - rating: syzoj.config.default.user.rating, - register_time: parseInt((new Date()).getTime() / 1000), - prefer_formatted_code: true - }, val))); - } - - static async fromEmail(email) { - return User.fromRecord(User.model.findOne({ - where: { - email: email - } - })); - } - - static async fromName(name) { - return User.fromRecord(User.model.findOne({ - where: { - username: name - } - })); - } - - async isAllowedEditBy(user) { - if (!user) return false; - if (await user.hasPrivilege('manage_user')) return true; - return user && (user.is_admin || this.id === user.id); - } - - async refreshSubmitInfo() { - await syzoj.utils.lock(['User::refreshSubmitInfo', this.id], async () => { - let JudgeState = syzoj.model('judge_state'); - this.ac_num = await JudgeState.model.count({ - col: 'problem_id', - distinct: true, - where: { - user_id: this.id, - status: 'Accepted', - type: { - $ne: 1 // Not a contest submission - } - } - }); - - this.submit_num = await JudgeState.count({ - user_id: this.id, - type: { - $ne: 1 // Not a contest submission - } - }); - - await this.save(); - }); - } - - async getACProblems() { - let JudgeState = syzoj.model('judge_state'); - - let queryResult = await JudgeState.model.aggregate('problem_id', 'DISTINCT', { - plain: false, - where: { - user_id: this.id, - status: 'Accepted', - type: { - $ne: 1 // Not a contest submissio - } - }, - order: [["problem_id", "ASC"]] - }); - - return queryResult.map(record => record['DISTINCT']) - } - - async getArticles() { - let Article = syzoj.model('article'); - - let all = await Article.model.findAll({ - attributes: ['id', 'title', 'public_time'], - where: { - user_id: this.id - } - }); - - return all.map(x => ({ - id: x.get('id'), - title: x.get('title'), - public_time: x.get('public_time') - })); - } - - async getStatistics() { - let JudgeState = syzoj.model('judge_state'); - - let statuses = { - "Accepted": ["Accepted"], - "Wrong Answer": ["Wrong Answer", "File Error", "Output Limit Exceeded"], - "Runtime Error": ["Runtime Error"], - "Time Limit Exceeded": ["Time Limit Exceeded"], - "Memory Limit Exceeded": ["Memory Limit Exceeded"], - "Compile Error": ["Compile Error"] - }; - - let res = {}; - for (let status in statuses) { - res[status] = 0; - for (let s of statuses[status]) { - res[status] += await JudgeState.count({ - user_id: this.id, - type: 0, - status: s - }); - } - } - - return res; - } - - async renderInformation() { - this.information = await syzoj.utils.markdown(this.information); - } - - async getPrivileges() { - let UserPrivilege = syzoj.model('user_privilege'); - let privileges = await UserPrivilege.query(null, { - user_id: this.id - }); - - return privileges.map(x => x.privilege); - } - - async setPrivileges(newPrivileges) { - let UserPrivilege = syzoj.model('user_privilege'); - - let oldPrivileges = await this.getPrivileges(); - - let delPrivileges = oldPrivileges.filter(x => !newPrivileges.includes(x)); - let addPrivileges = newPrivileges.filter(x => !oldPrivileges.includes(x)); - - for (let privilege of delPrivileges) { - let obj = await UserPrivilege.findOne({ where: { - user_id: this.id, - privilege: privilege - } }); - - await obj.destroy(); - } - - for (let privilege of addPrivileges) { - let obj = await UserPrivilege.create({ - user_id: this.id, - privilege: privilege - }); - - await obj.save(); - } - } - - async hasPrivilege(privilege) { - if (this.is_admin) return true; - - let UserPrivilege = syzoj.model('user_privilege'); - let x = await UserPrivilege.findOne({ where: { user_id: this.id, privilege: privilege } }); - return !(!x); - } - - async getLastSubmitLanguage() { - let JudgeState = syzoj.model('judge_state'); - - let a = await JudgeState.query([1, 1], { user_id: this.id }, [['submit_time', 'desc']]); - if (a[0]) return a[0].language; - - return null; - } - - getModel() { return model; } -} - -User.model = model; - -module.exports = User; diff --git a/models/user.ts b/models/user.ts new file mode 100644 index 0000000..48a4c58 --- /dev/null +++ b/models/user.ts @@ -0,0 +1,205 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +declare var syzoj: any; + +import JudgeState from "./judge_state"; +import UserPrivilege from "./user_privilege"; +import Article from "./article"; + +@TypeORM.Entity() +export default class User extends Model { + @TypeORM.PrimaryGeneratedColumn() + id: number; + + @TypeORM.Index({ unique: true }) + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + username: string; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 120 }) + email: string; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 120 }) + password: string; + + @TypeORM.Column({ nullable: true, type: "varchar", length: 80 }) + nickname: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + nameplate: string; + + @TypeORM.Column({ nullable: true, type: "text" }) + information: string; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + ac_num: number; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "integer" }) + submit_num: number; + + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_admin: boolean; + + @TypeORM.Index() + @TypeORM.Column({ nullable: true, type: "boolean" }) + is_show: boolean; + + @TypeORM.Column({ nullable: true, type: "boolean", default: true }) + public_email: boolean; + + @TypeORM.Column({ nullable: true, type: "boolean", default: true }) + prefer_formatted_code: boolean; + + @TypeORM.Column({ nullable: true, type: "integer" }) + sex: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + rating: number; + + @TypeORM.Column({ nullable: true, type: "integer" }) + register_time: number; + + static async fromEmail(email): Promise { + return User.findOne({ + where: { + email: email + } + }); + } + + static async fromName(name): Promise { + return User.findOne({ + where: { + username: name + } + }); + } + + async isAllowedEditBy(user) { + if (!user) return false; + if (await user.hasPrivilege('manage_user')) return true; + return user && (user.is_admin || this.id === user.id); + } + + getQueryBuilderForACProblems() { + return JudgeState.createQueryBuilder() + .select(`DISTINCT(problem_id)`) + .where('user_id = :user_id', { user_id: this.id }) + .andWhere('status = :status', { status: 'Accepted' }) + .andWhere('type != 1') + .orderBy({ problem_id: 'ASC' }) + } + + async refreshSubmitInfo() { + await syzoj.utils.lock(['User::refreshSubmitInfo', this.id], async () => { + this.ac_num = await JudgeState.countQuery(this.getQueryBuilderForACProblems()); + this.submit_num = await JudgeState.count({ + user_id: this.id, + type: TypeORM.Not(1) // Not a contest submission + }); + + await this.save(); + }); + } + + async getACProblems() { + let queryResult = await this.getQueryBuilderForACProblems().getRawMany(); + + return queryResult.map(record => record['problem_id']) + } + + async getArticles() { + return await Article.find({ + where: { + user_id: this.id + } + }); + } + + async getStatistics() { + let statuses = { + "Accepted": ["Accepted"], + "Wrong Answer": ["Wrong Answer", "File Error", "Output Limit Exceeded"], + "Runtime Error": ["Runtime Error"], + "Time Limit Exceeded": ["Time Limit Exceeded"], + "Memory Limit Exceeded": ["Memory Limit Exceeded"], + "Compile Error": ["Compile Error"] + }; + + let res = {}; + for (let status in statuses) { + res[status] = 0; + for (let s of statuses[status]) { + res[status] += await JudgeState.count({ + user_id: this.id, + type: 0, + status: s + }); + } + } + + return res; + } + + async renderInformation() { + this.information = await syzoj.utils.markdown(this.information); + } + + async getPrivileges() { + let privileges = await UserPrivilege.find({ + where: { + user_id: this.id + } + }); + + return privileges.map(x => x.privilege); + } + + async setPrivileges(newPrivileges) { + let oldPrivileges = await this.getPrivileges(); + + let delPrivileges = oldPrivileges.filter(x => !newPrivileges.includes(x)); + let addPrivileges = newPrivileges.filter(x => !oldPrivileges.includes(x)); + + for (let privilege of delPrivileges) { + let obj = await UserPrivilege.findOne({ where: { + user_id: this.id, + privilege: privilege + } }); + + await obj.destroy(); + } + + for (let privilege of addPrivileges) { + let obj = await UserPrivilege.create({ + user_id: this.id, + privilege: privilege + }); + + await obj.save(); + } + } + + async hasPrivilege(privilege) { + if (this.is_admin) return true; + + let x = await UserPrivilege.findOne({ where: { user_id: this.id, privilege: privilege } }); + return !!x; + } + + async getLastSubmitLanguage() { + let a = await JudgeState.findOne({ + where: { + user_id: this.id + }, + order: { + submit_time: 'DESC' + } + }); + if (a) return a.language; + + return null; + } +} diff --git a/models/user_privilege.js b/models/user_privilege.js deleted file mode 100644 index eb889a8..0000000 --- a/models/user_privilege.js +++ /dev/null @@ -1,37 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let model = db.define('user_privilege', { - user_id: { type: Sequelize.INTEGER, primaryKey: true }, - privilege: { - type: Sequelize.STRING, - primaryKey: true - } -}, { - timestamps: false, - tableName: 'user_privilege', - indexes: [ - { - fields: ['user_id'] - }, - { - fields: ['privilege'] - } - ] -}); - -let Model = require('./common'); -class UserPrivilege extends Model { - static async create(val) { - return UserPrivilege.fromRecord(UserPrivilege.model.build(Object.assign({ - user_id: 0, - privilege: '' - }, val))); - } - - getModel() { return model; } -} - -UserPrivilege.model = model; - -module.exports = UserPrivilege; diff --git a/models/user_privilege.ts b/models/user_privilege.ts new file mode 100644 index 0000000..fcb997c --- /dev/null +++ b/models/user_privilege.ts @@ -0,0 +1,13 @@ +import * as TypeORM from "typeorm"; +import Model from "./common"; + +@TypeORM.Entity() +export default class UserPrivilege extends Model { + @TypeORM.Index() + @TypeORM.Column({ type: "integer", primary: true }) + user_id: number; + + @TypeORM.Index() + @TypeORM.Column({ type: "varchar", length: 80, primary: true }) + privilege: string; +} diff --git a/models/waiting_judge.js b/models/waiting_judge.js deleted file mode 100644 index 312074b..0000000 --- a/models/waiting_judge.js +++ /dev/null @@ -1,50 +0,0 @@ -let Sequelize = require('sequelize'); -let db = syzoj.db; - -let JudgeState = syzoj.model('judge_state'); -let CustomTest = syzoj.model('custom_test'); - -let model = db.define('waiting_judge', { - id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - judge_id: { type: Sequelize.INTEGER }, - - // Smaller is higher - priority: { type: Sequelize.INTEGER }, - - type: { - type: Sequelize.ENUM, - values: ['submission', 'custom-test'] - } -}, { - timestamps: false, - tableName: 'waiting_judge', - indexes: [ - { - fields: ['judge_id'], - } - ] -}); - -let Model = require('./common'); -class WaitingJudge extends Model { - static async create(val) { - return WaitingJudge.fromRecord(WaitingJudge.model.build(Object.assign({ - judge_id: 0, - priority: 0 - }, val))); - } - - async getCustomTest() { - return CustomTest.fromID(this.judge_id); - } - - async getJudgeState() { - return JudgeState.fromID(this.judge_id); - } - - getModel() { return model; } -} - -WaitingJudge.model = model; - -module.exports = WaitingJudge; diff --git a/modules/admin.js b/modules/admin.js index 23372f4..e9917c5 100644 --- a/modules/admin.js +++ b/modules/admin.js @@ -9,14 +9,14 @@ const RatingHistory = syzoj.model('rating_history'); let ContestPlayer = syzoj.model('contest_player'); const calcRating = require('../libs/rating'); -let db = syzoj.db; - app.get('/admin/info', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); let allSubmissionsCount = await JudgeState.count(); - let todaySubmissionsCount = await JudgeState.count({ submit_time: { $gte: syzoj.utils.getCurrentDate(true) } }); + let todaySubmissionsCount = await JudgeState.count({ + submit_time: TypeORM.MoreThanOrEqual(syzoj.utils.getCurrentDate(true)) + }); let problemsCount = await Problem.count(); let articlesCount = await Article.count(); let contestsCount = await Contest.count(); @@ -119,12 +119,12 @@ app.get('/admin/privilege', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); - let a = await UserPrivilege.query(); + let a = await UserPrivilege.find(); let users = {}; for (let p of a) { if (!users[p.user_id]) { users[p.user_id] = { - user: await User.fromID(p.user_id), + user: await User.findById(p.user_id), privileges: [] }; } @@ -149,7 +149,7 @@ app.post('/admin/privilege', async (req, res) => { let data = JSON.parse(req.body.data); for (let id in data) { - let user = await User.fromID(id); + let user = await User.findById(id); if (!user) throw new ErrorMessage(`不存在 ID 为 ${id} 的用户。`); await user.setPrivileges(data[id]); } @@ -166,9 +166,16 @@ app.post('/admin/privilege', async (req, res) => { app.get('/admin/rating', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); - const contests = await Contest.query(null, {}, [['start_time', 'desc']]); - const calcs = await RatingCalculation.query(null, {}, [['id', 'desc']]); - const util = require('util'); + const contests = await Contest.find({ + order: { + start_time: 'DESC' + } + }); + const calcs = await RatingCalculation.find({ + order: { + id: 'DESC' + } + }); for (const calc of calcs) await calc.loadRelationships(); res.render('admin_rating', { @@ -186,7 +193,7 @@ app.get('/admin/rating', async (req, res) => { app.post('/admin/rating/add', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); - const contest = await Contest.fromID(req.body.contest); + const contest = await Contest.findById(req.body.contest); if (!contest) throw new ErrorMessage('无此比赛'); await contest.loadRelationships(); @@ -199,7 +206,7 @@ app.post('/admin/rating/add', async (req, res) => { const players = []; for (let i = 1; i <= contest.ranklist.ranklist.player_num; i++) { - const user = await User.fromID((await ContestPlayer.fromID(contest.ranklist.ranklist[i])).user_id); + const user = await User.findById((await ContestPlayer.findById(contest.ranklist.ranklist[i])).user_id); players.push({ user: user, rank: i, @@ -227,7 +234,14 @@ app.post('/admin/rating/add', async (req, res) => { app.post('/admin/rating/delete', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); - const calcList = await RatingCalculation.query(null, { id: { $gte: req.body.calc_id } }, [['id', 'desc']]); + const calcList = await RatingCalculation.find({ + where: { + id: TypeORM.MoreThanOrEqual(req.body.calc_id) + }, + order: { + id: 'DESC' + } + }); if (calcList.length === 0) throw new ErrorMessage('ID 不正确'); for (let i = 0; i < calcList.length; i++) { @@ -277,17 +291,17 @@ app.post('/admin/other', async (req, res) => { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); if (req.body.type === 'reset_count') { - const problems = await Problem.query(); + const problems = await Problem.find(); for (const p of problems) { await p.resetSubmissionCount(); } } else if (req.body.type === 'reset_discussion') { - const articles = await Article.query(); + const articles = await Article.find(); for (const a of articles) { await a.resetReplyCountAndTime(); } } else if (req.body.type === 'reset_codelen') { - const submissions = await JudgeState.query(); + const submissions = await JudgeState.find(); for (const s of submissions) { if (s.type !== 'submit-answer') { s.code_length = s.code.length; @@ -310,60 +324,55 @@ app.post('/admin/rejudge', async (req, res) => { try { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); + let query = JudgeState.createQueryBuilder(); + let user = await User.fromName(req.body.submitter || ''); - let where = {}; - if (user) where.user_id = user.id; - else if (req.body.submitter) where.user_id = -1; + if (user) { + query.andWhere('user_id = :user_id', { user_id: user.id }); + } else if (req.body.submitter) { + query.andWhere('user_id = :user_id', { user_id: 0 }); + } let minID = parseInt(req.body.min_id); - if (isNaN(minID)) minID = 0; + if (!isNaN(minID)) query.andWhere('id >= :minID', { minID }) let maxID = parseInt(req.body.max_id); - if (isNaN(maxID)) maxID = 2147483647; - - where.id = { - $and: { - $gte: parseInt(minID), - $lte: parseInt(maxID) - } - }; + if (!isNaN(maxID)) query.andWhere('id <= :maxID', { maxID }) let minScore = parseInt(req.body.min_score); - if (isNaN(minScore)) minScore = 0; + if (!isNaN(minScore)) query.andWhere('score >= :minScore', { minScore }); let maxScore = parseInt(req.body.max_score); - if (isNaN(maxScore)) maxScore = 100; - - if (!(minScore === 0 && maxScore === 100)) { - where.score = { - $and: { - $gte: parseInt(minScore), - $lte: parseInt(maxScore) - } - }; - } + if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore }); let minTime = syzoj.utils.parseDate(req.body.min_time); - if (isNaN(minTime)) minTime = 0; + if (!isNaN(minTime)) query.andWhere('submit_time >= :minTime', { minTime: parseInt(minTime) }); let maxTime = syzoj.utils.parseDate(req.body.max_time); - if (isNaN(maxTime)) maxTime = 2147483647; + if (!isNaN(maxTime)) query.andWhere('submit_time <= :maxTime', { maxTime: parseInt(maxTime) }); - where.submit_time = { - $and: { - $gte: parseInt(minTime), - $lte: parseInt(maxTime) + if (req.body.language) { + if (req.body.language === 'submit-answer') { + query.andWhere(new TypeORM.Brackets(qb => { + qb.orWhere('language = :language', { language: '' }) + .orWhere('language IS NULL'); + })); + } else if (req.body.language === 'non-submit-answer') { + query.andWhere('language != :language', { language: '' }) + .andWhere('language IS NOT NULL');; + } else { + query.andWhere('language = :language', { language: req.body.language }); } - }; + } - if (req.body.language) { - if (req.body.language === 'submit-answer') where.language = { $or: [{ $eq: '', }, { $eq: null }] }; - else if (req.body.language === 'non-submit-answer') where.language = { $not: '' }; - else where.language = req.body.language; + if (req.body.status) { + query.andWhere('status LIKE :status', { status: req.body.status + '%' }); + } + + if (req.body.problem_id) { + query.andWhere('problem_id = :problem_id', { problem_id: parseInt(req.body.problem_id) || 0 }) } - if (req.body.status) where.status = { $like: req.body.status + '%' }; - if (req.body.problem_id) where.problem_id = parseInt(req.body.problem_id) || -1; - let count = await JudgeState.count(where); + let count = await JudgeState.countQuery(query); if (req.body.type === 'rejudge') { - let submissions = await JudgeState.query(null, where); + let submissions = await JudgeState.queryAll(query); for (let submission of submissions) { await submission.rejudge(); } diff --git a/modules/api.js b/modules/api.js index fad6caf..6abaaf4 100644 --- a/modules/api.js +++ b/modules/api.js @@ -113,7 +113,9 @@ app.post('/api/sign_up', async (req, res) => { username: req.body.username, password: req.body.password, email: req.body.email, - public_email: true + is_show: syzoj.config.default.user.show, + rating: syzoj.config.default.user.rating, + register_time: parseInt((new Date()).getTime() / 1000) }); await user.save(); @@ -158,7 +160,7 @@ app.post('/api/reset_password', async (req, res) => { let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2'; if (req.body.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。'); - const user = await User.fromID(obj.userId); + const user = await User.findById(obj.userId); user.password = req.body.password; await user.save(); @@ -198,7 +200,9 @@ app.get('/api/sign_up_confirm', async (req, res) => { username: obj.username, password: obj.password, email: obj.email, - public_email: true + is_show: syzoj.config.default.user.show, + rating: syzoj.config.default.user.rating, + register_time: parseInt((new Date()).getTime() / 1000) }); await user.save(); diff --git a/modules/api_v2.js b/modules/api_v2.js index eb5522f..2e88299 100644 --- a/modules/api_v2.js +++ b/modules/api_v2.js @@ -4,19 +4,23 @@ app.get('/api/v2/search/users/:keyword*?', async (req, res) => { let keyword = req.params.keyword || ''; let conditions = []; - const uid = parseInt(keyword); + const uid = parseInt(keyword) || 0; + if (uid != null && !isNaN(uid)) { conditions.push({ id: uid }); } if (keyword != null && String(keyword).length >= 2) { - conditions.push({ username: { $like: `%${req.params.keyword}%` } }); + conditions.push({ username: TypeORM.Like(`%${req.params.keyword}%`) }); } if (conditions.length === 0) { res.send({ success: true, results: [] }); } else { - let users = await User.query(null, { - $or: conditions - }, [['username', 'asc']]); + let users = await User.find({ + where: conditions, + order: { + username: 'ASC' + } + }); let result = []; @@ -34,15 +38,20 @@ app.get('/api/v2/search/problems/:keyword*?', async (req, res) => { let Problem = syzoj.model('problem'); let keyword = req.params.keyword || ''; - let problems = await Problem.query(null, { - title: { $like: `%${req.params.keyword}%` } - }, [['id', 'asc']]); + let problems = await Problem.find({ + where: { + title: TypeORM.Like(`%${req.params.keyword}%`) + }, + order: { + id: 'ASC' + } + }); let result = []; let id = parseInt(keyword); if (id) { - let problemById = await Problem.fromID(parseInt(keyword)); + let problemById = await Problem.findById(parseInt(keyword)); if (problemById && await problemById.isAllowedUseBy(res.locals.user)) { result.push(problemById); } @@ -67,9 +76,14 @@ app.get('/api/v2/search/tags/:keyword*?', async (req, res) => { let ProblemTag = syzoj.model('problem_tag'); let keyword = req.params.keyword || ''; - let tags = await ProblemTag.query(null, { - name: { $like: `%${req.params.keyword}%` } - }, [['name', 'asc']]); + let tags = await ProblemTag.find({ + where: { + name: TypeORM.Like(`%${req.params.keyword}%`) + }, + order: { + name: 'ASC' + } + }); let result = tags.slice(0, syzoj.config.page.edit_problem_tag_list); diff --git a/modules/contest.js b/modules/contest.js index 763d3d8..33b76a9 100644 --- a/modules/contest.js +++ b/modules/contest.js @@ -15,7 +15,9 @@ app.get('/contests', async (req, res) => { else where = { is_public: true }; let paginate = syzoj.utils.paginate(await Contest.count(where), req.query.page, syzoj.config.page.contest); - let contests = await Contest.query(paginate, where, [['start_time', 'desc']]); + let contests = await Contest.queryPage(paginate, where, { + start_time: 'DESC' + }); await contests.forEachAsync(async x => x.subtitle = await syzoj.utils.markdown(x.subtitle)); @@ -36,7 +38,7 @@ app.get('/contest/:id/edit', async (req, res) => { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); if (!contest) { contest = await Contest.create(); contest.id = 0; @@ -45,8 +47,8 @@ app.get('/contest/:id/edit', async (req, res) => { } let problems = [], admins = []; - if (contest.problems) problems = await contest.problems.split('|').mapAsync(async id => await Problem.fromID(id)); - if (contest.admins) admins = await contest.admins.split('|').mapAsync(async id => await User.fromID(id)); + if (contest.problems) problems = await contest.problems.split('|').mapAsync(async id => await Problem.findById(id)); + if (contest.admins) admins = await contest.admins.split('|').mapAsync(async id => await User.findById(id)); res.render('contest_edit', { contest: contest, @@ -66,7 +68,7 @@ app.post('/contest/:id/edit', async (req, res) => { if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); let ranklist = null; if (!contest) { contest = await Contest.create(); @@ -120,7 +122,7 @@ app.get('/contest/:id', async (req, res) => { const curUser = res.locals.user; let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); if (!contest) throw new ErrorMessage('无此比赛。'); if (!contest.is_public && (!res.locals.user || !res.locals.user.is_admin)) throw new ErrorMessage('比赛未公开,请耐心等待 (´∀ `)'); @@ -131,7 +133,7 @@ app.get('/contest/:id', async (req, res) => { 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 problems = await problems_id.mapAsync(async id => await Problem.findById(id)); let player = null; @@ -147,7 +149,7 @@ app.get('/contest/:id', async (req, res) => { for (let problem of problems) { if (contest.type === 'noi') { if (player.score_details[problem.problem.id]) { - let judge_state = await JudgeState.fromID(player.score_details[problem.problem.id].judge_id); + let judge_state = await JudgeState.findById(player.score_details[problem.problem.id].judge_id); problem.status = judge_state.status; if (!contest.ended && !await problem.problem.isAllowedEditBy(res.locals.user) && !['Compile Error', 'Waiting', 'Compiling'].includes(problem.status)) { problem.status = 'Submitted'; @@ -156,7 +158,7 @@ app.get('/contest/:id', async (req, res) => { } } else if (contest.type === 'ioi') { if (player.score_details[problem.problem.id]) { - let judge_state = await JudgeState.fromID(player.score_details[problem.problem.id].judge_id); + let judge_state = await JudgeState.findById(player.score_details[problem.problem.id].judge_id); problem.status = judge_state.status; problem.judge_id = player.score_details[problem.problem.id].judge_id; await contest.loadRelationships(); @@ -222,7 +224,7 @@ app.get('/contest/:id', async (req, res) => { app.get('/contest/:id/ranklist', async (req, res) => { try { let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); const curUser = res.locals.user; if (!contest) throw new ErrorMessage('无此比赛。'); @@ -238,14 +240,14 @@ app.get('/contest/:id/ranklist', async (req, res) => { 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 player = await ContestPlayer.findById(player_id); if (contest.type === 'noi' || contest.type === 'ioi') { player.score = 0; } for (let i in player.score_details) { - player.score_details[i].judge_state = await JudgeState.fromID(player.score_details[i].judge_id); + player.score_details[i].judge_state = await JudgeState.findById(player.score_details[i].judge_id); /*** XXX: Clumsy duplication, see ContestRanklist::updatePlayer() ***/ if (contest.type === 'noi' || contest.type === 'ioi') { @@ -255,7 +257,7 @@ app.get('/contest/:id/ranklist', async (req, res) => { } } - let user = await User.fromID(player.user_id); + let user = await User.findById(player.user_id); return { user: user, @@ -264,7 +266,7 @@ app.get('/contest/:id/ranklist', async (req, res) => { }); let problems_id = await contest.getProblems(); - let problems = await problems_id.mapAsync(async id => await Problem.fromID(id)); + let problems = await problems_id.mapAsync(async id => await Problem.findById(id)); res.render('contest_ranklist', { contest: contest, @@ -296,7 +298,7 @@ function getDisplayConfig(contest) { app.get('/contest/:id/submissions', async (req, res) => { try { let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); if (!contest.is_public && (!res.locals.user || !res.locals.user.is_admin)) throw new ErrorMessage('比赛未公开,请耐心等待 (´∀ `)'); if (contest.isEnded()) { @@ -309,56 +311,68 @@ app.get('/contest/:id/submissions', async (req, res) => { const curUser = res.locals.user; let user = req.query.submitter && await User.fromName(req.query.submitter); - let where = { - submit_time: { $gte: contest.start_time, $lte: contest.end_time } - }; + + let query = JudgeState.createQueryBuilder(); + + let isFiltered = false; if (displayConfig.showOthers) { if (user) { - where.user_id = user.id; + query.andWhere('user_id = :user_id', { user_id: user.id }); + isFiltered = true; } } else { if (curUser == null || // Not logined (user && user.id !== curUser.id)) { // Not querying himself - throw new ErrorMessage("您没有权限执行此操作"); + throw new ErrorMessage("您没有权限执行此操作。"); } - where.user_id = curUser.id; + query.andWhere('user_id = :user_id', { user_id: curUser.id }); + isFiltered = true; } if (displayConfig.showScore) { - let minScore = parseInt(req.query.min_score); - let maxScore = parseInt(req.query.max_score); - - if (!isNaN(minScore) || !isNaN(maxScore)) { - if (isNaN(minScore)) minScore = 0; - if (isNaN(maxScore)) maxScore = 100; - if (!(minScore === 0 && maxScore === 100)) { - where.score = { - $and: { - $gte: parseInt(minScore), - $lte: parseInt(maxScore) - } - }; - } - } + let minScore = parseInt(req.body.min_score); + if (!isNaN(minScore)) query.andWhere('score >= :minScore', { minScore }); + let maxScore = parseInt(req.body.max_score); + if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore }); + + if (!isNaN(minScore) || !isNaN(maxScore)) isFiltered = true; } if (req.query.language) { - if (req.query.language === 'submit-answer') where.language = ''; - else where.language = req.query.language; + if (req.body.language === 'submit-answer') { + query.andWhere(new TypeORM.Brackets(qb => { + qb.orWhere('language = :language', { language: '' }) + .orWhere('language IS NULL'); + })); + } else if (req.body.language === 'non-submit-answer') { + query.andWhere('language != :language', { language: '' }) + .andWhere('language IS NOT NULL'); + } else { + query.andWhere('language = :language', { language: req.body.language }) + } + isFiltered = true; } if (displayConfig.showResult) { - if (req.query.status) where.status = { $like: req.query.status + '%' }; + if (req.query.status) { + query.andWhere('status LIKE :status', { status: req.query.status + '%' }); + isFiltered = true; + } } - if (req.query.problem_id) where.problem_id = problems_id[parseInt(req.query.problem_id) - 1]; - where.type = 1; - where.type_info = contest_id; + if (req.query.problem_id) { + problem_id = problems_id[parseInt(req.query.problem_id) - 1] || 0; + query.andWhere('problem_id = :problem_id', { problem_id }) + isFiltered = true; + } - let isFiltered = !!(where.problem_id || where.user_id || where.score || where.language || where.status); + query.andWhere('type = 1') + .andWhere('type_info = :contest_id', { contest_id }); - 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']]); + let paginate = syzoj.utils.paginate(await JudgeState.countQuery(query), req.query.page, syzoj.config.page.judge_state); + let judge_state = await JudgeState.queryPage(paginate, query, { + submit_time: 'DESC' + }); await judge_state.forEachAsync(async obj => { await obj.loadRelationships(); @@ -397,7 +411,7 @@ app.get('/contest/:id/submissions', async (req, res) => { app.get('/contest/submission/:id', async (req, res) => { try { const id = parseInt(req.params.id); - const judge = await JudgeState.fromID(id); + const judge = await JudgeState.findById(id); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。"); const curUser = res.locals.user; if ((!curUser) || judge.user_id !== curUser.id) throw new ErrorMessage("您没有权限执行此操作。"); @@ -406,7 +420,7 @@ app.get('/contest/submission/:id', async (req, res) => { return res.redirect(syzoj.utils.makeUrl(['submission', id])); } - const contest = await Contest.fromID(judge.type_info); + const contest = await Contest.findById(judge.type_info); contest.ended = contest.isEnded(); const displayConfig = getDisplayConfig(contest); @@ -448,7 +462,7 @@ app.get('/contest/submission/:id', async (req, res) => { app.get('/contest/:id/problem/:pid', async (req, res) => { try { let contest_id = parseInt(req.params.id); - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); if (!contest) throw new ErrorMessage('无此比赛。'); const curUser = res.locals.user; @@ -458,7 +472,7 @@ app.get('/contest/:id/problem/:pid', async (req, res) => { if (!pid || pid < 1 || pid > problems_id.length) throw new ErrorMessage('无此题目。'); let problem_id = problems_id[pid - 1]; - let problem = await Problem.fromID(problem_id); + let problem = await Problem.findById(problem_id); await problem.loadRelationships(); contest.ended = contest.isEnded(); @@ -497,7 +511,7 @@ app.get('/contest/:id/problem/:pid', async (req, res) => { app.get('/contest/:id/:pid/download/additional_file', async (req, res) => { try { let id = parseInt(req.params.id); - let contest = await Contest.fromID(id); + let contest = await Contest.findById(id); if (!contest) throw new ErrorMessage('无此比赛。'); let problems_id = await contest.getProblems(); @@ -506,7 +520,7 @@ app.get('/contest/:id/:pid/download/additional_file', async (req, res) => { if (!pid || pid < 1 || pid > problems_id.length) throw new ErrorMessage('无此题目。'); let problem_id = problems_id[pid - 1]; - let problem = await Problem.fromID(problem_id); + let problem = await Problem.findById(problem_id); contest.ended = contest.isEnded(); if (!(contest.isRunning() || contest.isEnded())) { diff --git a/modules/discussion.js b/modules/discussion.js index a0984e5..a004705 100644 --- a/modules/discussion.js +++ b/modules/discussion.js @@ -12,17 +12,19 @@ app.get('/discussion/:type?', async (req, res) => { let where; if (in_problems) { - where = { problem_id: { $not: null } }; + where = { problem_id: TypeORM.Not(null) }; } else { - where = { problem_id: { $eq: null } }; + where = { problem_id: null }; } let paginate = syzoj.utils.paginate(await Article.count(where), req.query.page, syzoj.config.page.discussion); - let articles = await Article.query(paginate, where, [['sort_time', 'desc']]); + let articles = await Article.queryPage(paginate, where, { + sort_time: 'DESC' + }); for (let article of articles) { await article.loadRelationships(); if (in_problems) { - article.problem = await Problem.fromID(article.problem_id); + article.problem = await Problem.findById(article.problem_id); } } @@ -43,7 +45,7 @@ app.get('/discussion/:type?', async (req, res) => { app.get('/discussion/problem/:pid', async (req, res) => { try { let pid = parseInt(req.params.pid); - let problem = await Problem.fromID(pid); + let problem = await Problem.findById(pid); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedUseBy(res.locals.user)) { throw new ErrorMessage('您没有权限进行此操作。'); @@ -51,7 +53,9 @@ app.get('/discussion/problem/:pid', async (req, res) => { let where = { problem_id: pid }; let paginate = syzoj.utils.paginate(await Article.count(where), req.query.page, syzoj.config.page.discussion); - let articles = await Article.query(paginate, where, [['sort_time', 'desc']]); + let articles = await Article.queryPage(paginate, where, { + sort_time: 'DESC' + }); for (let article of articles) await article.loadRelationships(); @@ -72,7 +76,7 @@ app.get('/discussion/problem/:pid', async (req, res) => { app.get('/article/:id', async (req, res) => { try { let id = parseInt(req.params.id); - let article = await Article.fromID(id); + let article = await Article.findById(id); if (!article) throw new ErrorMessage('无此帖子。'); await article.loadRelationships(); @@ -84,7 +88,9 @@ app.get('/article/:id', async (req, res) => { let commentsCount = await ArticleComment.count(where); let paginate = syzoj.utils.paginate(commentsCount, req.query.page, syzoj.config.page.article_comment); - let comments = await ArticleComment.query(paginate, where, [['public_time', 'desc']]); + let comments = await ArticleComment.queryPage(paginate, where, { + public_time: 'DESC' + }); for (let comment of comments) { comment.content = await syzoj.utils.markdown(comment.content); @@ -94,7 +100,7 @@ app.get('/article/:id', async (req, res) => { let problem = null; if (article.problem_id) { - problem = await Problem.fromID(article.problem_id); + problem = await Problem.findById(article.problem_id); if (!await problem.isAllowedUseBy(res.locals.user)) { throw new ErrorMessage('您没有权限进行此操作。'); } @@ -120,7 +126,7 @@ app.get('/article/:id/edit', async (req, res) => { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); - let article = await Article.fromID(id); + let article = await Article.findById(id); if (!article) { article = await Article.create(); @@ -146,7 +152,7 @@ app.post('/article/:id/edit', async (req, res) => { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); - let article = await Article.fromID(id); + let article = await Article.findById(id); let time = syzoj.utils.getCurrentDate(); if (!article) { @@ -155,7 +161,7 @@ app.post('/article/:id/edit', async (req, res) => { article.public_time = article.sort_time = time; if (req.query.problem_id) { - let problem = await Problem.fromID(req.query.problem_id); + let problem = await Problem.findById(req.query.problem_id); if (!problem) throw new ErrorMessage('无此题目。'); article.problem_id = problem.id; } else { @@ -187,7 +193,7 @@ app.post('/article/:id/delete', async (req, res) => { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); - let article = await Article.fromID(id); + let article = await Article.findById(id); if (!article) { throw new ErrorMessage('无此帖子。'); @@ -211,7 +217,7 @@ app.post('/article/:id/comment', async (req, res) => { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); - let article = await Article.fromID(id); + let article = await Article.findById(id); if (!article) { throw new ErrorMessage('无此帖子。'); @@ -246,7 +252,7 @@ app.post('/article/:article_id/comment/:id/delete', async (req, res) => { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); let id = parseInt(req.params.id); - let comment = await ArticleComment.fromID(id); + let comment = await ArticleComment.findById(id); if (!comment) { throw new ErrorMessage('无此评论。'); diff --git a/modules/index.js b/modules/index.js index 8782c38..b0ca11b 100644 --- a/modules/index.js +++ b/modules/index.js @@ -10,10 +10,15 @@ const timeAgo = new TimeAgo('zh-CN'); app.get('/', async (req, res) => { try { - let ranklist = await User.query([1, syzoj.config.page.ranklist_index], { is_show: true }, [[syzoj.config.sorting.ranklist.field, syzoj.config.sorting.ranklist.order]]); + let ranklist = await User.queryRange([1, syzoj.config.page.ranklist_index], { is_show: true }, { + [syzoj.config.sorting.ranklist.field]: syzoj.config.sorting.ranklist.order + }); await ranklist.forEachAsync(async x => x.renderInformation()); - let notices = (await Article.query(null, { is_notice: true }, [['public_time', 'desc']])).map(article => ({ + let notices = (await Article.find({ + where: { is_notice: true }, + order: { public_time: 'DESC' } + })).map(article => ({ title: article.title, url: syzoj.utils.makeUrl(['article', article.id]), date: syzoj.utils.formatDate(article.public_time, 'L') @@ -24,9 +29,13 @@ app.get('/', async (req, res) => { fortune = Divine(res.locals.user.username, res.locals.user.sex); } - let contests = await Contest.query([1, 5], { is_public: true }, [['start_time', 'desc']]); + let contests = await Contest.queryRange([1, 5], { is_public: true }, { + start_time: 'DESC' + }); - let problems = (await Problem.query([1, 5], { is_public: true }, [['publicize_time', 'desc']])).map(problem => ({ + let problems = (await Problem.queryRange([1, 5], { is_public: true }, { + publicize_time: 'DESC' + })).map(problem => ({ id: problem.id, title: problem.title, time: timeAgo.format(new Date(problem.publicize_time)), diff --git a/modules/problem.js b/modules/problem.js index d93cec2..22dadf3 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -1,13 +1,12 @@ let Problem = syzoj.model('problem'); let JudgeState = syzoj.model('judge_state'); let FormattedCode = syzoj.model('formatted_code'); -let CustomTest = syzoj.model('custom_test'); -let WaitingJudge = syzoj.model('waiting_judge'); let Contest = syzoj.model('contest'); let ProblemTag = syzoj.model('problem_tag'); -let ProblemTagMap = syzoj.model('problem_tag_map'); let Article = syzoj.model('article'); -const Sequelize = require('sequelize'); + +const randomstring = require('randomstring'); +const fs = require('fs-extra'); let Judger = syzoj.lib('judger'); let CodeFormatter = syzoj.lib('code_formatter'); @@ -20,28 +19,24 @@ app.get('/problems', async (req, res) => { throw new ErrorMessage('错误的排序参数。'); } - let sortVal = sort; - if (sort === 'ac_rate') { - sortVal = { raw: 'ac_num / submit_num' }; - } - let where = {}; + let query = Problem.createQueryBuilder(); if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) { if (res.locals.user) { - where = { - $or: { - is_public: 1, - user_id: res.locals.user.id - } - }; + query.where('is_public = 1') + .orWhere('user_id = :user_id', { user_id: res.locals.user.id }); } else { - where = { - is_public: 1 - }; + query.where('is_public = 1'); } } - let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem); - let problems = await Problem.query(paginate, where, [[sortVal, order]]); + if (sort === 'ac_rate') { + query = query.orderBy('ac_num / submit_num', order.toUpperCase()); + } else { + query = query.orderBy(sort, order.toUpperCase()); + } + + let paginate = syzoj.utils.paginate(await Problem.countQuery(query), req.query.page, syzoj.config.page.problem); + let problems = await Problem.queryPage(paginate, query); await problems.forEachAsync(async problem => { problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user); @@ -73,51 +68,51 @@ app.get('/problems/search', async (req, res) => { throw new ErrorMessage('错误的排序参数。'); } - let where = { - $or: { - title: { $like: `%${req.query.keyword}%` }, - id: id - } - }; - + let query = Problem.createQueryBuilder(); if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) { if (res.locals.user) { - where = { - $and: [ - where, - { - $or: { - is_public: 1, - user_id: res.locals.user.id - } - } - ] - }; + query.where(new TypeORM.Brackets(qb => { + qb.where('is_public = 1') + .orWhere('user_id = :user_id', { user_id: res.locals.user.id }) + })) + .andWhere(new TypeORM.Brackets(qb => { + qb.where('title LIKE :title', { title: `%${req.query.keyword}%` }) + .orWhere('id = :id', { id: id }) + })); } else { - where = { - $and: [ - where, - { - is_public: 1 - } - ] - }; + query.where('is_public = 1') + .andWhere(new TypeORM.Brackets(qb => { + qb.where('title LIKE :title', { title: `%${req.query.keyword}%` }) + .orWhere('id = :id', { id: id }) + })); } + } else { + query.where('title LIKE :title', { title: `%${req.query.keyword}%` }) + .orWhere('id = :id', { id: id }) } - let sortVal = sort; if (sort === 'ac_rate') { - sortVal = { raw: 'ac_num / submit_num' }; + query = query.orderBy('ac_num / submit_num', order.toUpperCase()); + } else { + query = query.orderBy(sort, order.toUpperCase()); } - let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem); - let problems = await Problem.query(paginate, where, [syzoj.db.literal('`id` = ' + id + ' DESC'), [sortVal, order]]); + let paginate = syzoj.utils.paginate(await Problem.countQuery(query), req.query.page, syzoj.config.page.problem); + let problems = await Problem.queryPage(paginate, query); - await problems.forEachAsync(async problem => { + let problemMatchedID = null; + problems = (await problems.mapAsync(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(); - }); + + if (problem.id === id) { + problemMatchedID = problem; + return null; + } else return problem; + })).filter(x => x); + + if (problemMatchedID) problems.unshift(problemMatchedID); res.render('problems', { allowedManageTag: res.locals.user && await res.locals.user.hasPrivilege('manage_problem_tag'), @@ -137,7 +132,7 @@ app.get('/problems/search', async (req, res) => { 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)); + let tags = await tagIDs.mapAsync(async tagID => ProblemTag.findById(tagID)); const sort = req.query.sort || syzoj.config.sorting.problem.field; const order = req.query.order || syzoj.config.sorting.problem.order; if (!['id', 'title', 'rating', 'ac_num', 'submit_num', 'ac_rate'].includes(sort) || !['asc', 'desc'].includes(order)) { @@ -157,7 +152,7 @@ app.get('/problems/tag/:tagIDs', async (req, res) => { } } - let sql = 'SELECT * FROM `problem` WHERE\n'; + let sql = 'SELECT `id` FROM `problem` WHERE\n'; for (let tagID of tagIDs) { if (tagID !== tagIDs[0]) { sql += 'AND\n'; @@ -174,13 +169,18 @@ app.get('/problems/tag/:tagIDs', async (req, res) => { } } - let paginate = syzoj.utils.paginate(await Problem.count(sql), req.query.page, syzoj.config.page.problem); + let paginate = syzoj.utils.paginate(await Problem.countQuery(sql), req.query.page, syzoj.config.page.problem); let problems = await Problem.query(sql + ` ORDER BY ${sortVal} ${order} ` + paginate.toSQL()); - await problems.forEachAsync(async problem => { + problems = await problems.mapAsync(async problem => { + // query() returns plain objects. + problem = await Problem.findById(problem.id); + problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user); problem.judge_state = await problem.getJudgeState(res.locals.user, true); problem.tags = await problem.getTags(); + + return problem; }); res.render('problems', { @@ -202,7 +202,7 @@ app.get('/problems/tag/:tagIDs', async (req, res) => { app.get('/problem/:id', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedUseBy(res.locals.user)) { @@ -245,7 +245,7 @@ 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); + let problem = await Problem.findById(id); if (!problem || !problem.is_public) throw new ErrorMessage('无此题目。'); let obj = { @@ -279,11 +279,15 @@ app.get('/problem/:id/export', async (req, res) => { app.get('/problem/:id/edit', async (req, res) => { try { let id = parseInt(req.params.id) || 0; - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); - problem = await Problem.create(); + problem = await Problem.create({ + time_limit: syzoj.config.default.problem.time_limit, + memory_limit: syzoj.config.default.problem.memory_limit, + type: 'traditional' + }); problem.id = id; problem.allowedEdit = true; problem.tags = []; @@ -310,16 +314,20 @@ app.get('/problem/:id/edit', async (req, res) => { app.post('/problem/:id/edit', async (req, res) => { try { let id = parseInt(req.params.id) || 0; - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); - problem = await Problem.create(); + problem = await Problem.create({ + time_limit: syzoj.config.default.problem.time_limit, + memory_limit: syzoj.config.default.problem.memory_limit, + type: 'traditional' + }); if (await res.locals.user.hasPrivilege('manage_problem')) { let customID = parseInt(req.body.id); if (customID) { - if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。'); + if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。'); problem.id = customID; } else if (id) problem.id = id; } @@ -333,7 +341,7 @@ app.post('/problem/:id/edit', async (req, res) => { if (await res.locals.user.hasPrivilege('manage_problem')) { let customID = parseInt(req.body.id); if (customID && customID !== id) { - if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。'); + if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。'); await problem.changeID(customID); } } @@ -357,7 +365,7 @@ app.post('/problem/:id/edit', async (req, res) => { req.body.tags = [req.body.tags]; } - 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.findById(x)); await problem.setTags(newTagIDs); res.redirect(syzoj.utils.makeUrl(['problem', problem.id])); @@ -372,12 +380,16 @@ app.post('/problem/:id/edit', async (req, res) => { app.get('/problem/:id/import', async (req, res) => { try { let id = parseInt(req.params.id) || 0; - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); - problem = await Problem.create(); + problem = await Problem.create({ + time_limit: syzoj.config.default.problem.time_limit, + memory_limit: syzoj.config.default.problem.memory_limit, + type: 'traditional' + }); problem.id = id; problem.new = true; problem.user_id = res.locals.user.id; @@ -403,16 +415,20 @@ app.get('/problem/:id/import', async (req, res) => { app.post('/problem/:id/import', async (req, res) => { try { let id = parseInt(req.params.id) || 0; - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) { if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); - problem = await Problem.create(); + problem = await Problem.create({ + time_limit: syzoj.config.default.problem.time_limit, + memory_limit: syzoj.config.default.problem.memory_limit, + type: 'traditional' + }); if (await res.locals.user.hasPrivilege('manage_problem')) { let customID = parseInt(req.body.id); if (customID) { - if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。'); + if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。'); problem.id = customID; } else if (id) problem.id = id; } @@ -460,15 +476,14 @@ app.post('/problem/:id/import', async (req, res) => { 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(req.body.url + (req.body.url.endsWith('/') ? 'testdata/download' : '/testdata/download')); - await fs.writeFileAsync(tmpFile.path, data); + await fs.writeFile(tmpFile.path, data); await problem.updateTestdata(tmpFile.path, await res.locals.user.hasPrivilege('manage_problem')); if (json.obj.have_additional_file) { let additional_file = await download(req.body.url + (req.body.url.endsWith('/') ? 'download/additional_file' : '/download/additional_file')); - await fs.writeFileAsync(tmpFile.path, additional_file); + await fs.writeFile(tmpFile.path, additional_file); await problem.updateFile(tmpFile.path, 'additional_file', await res.locals.user.hasPrivilege('manage_problem')); } } catch (e) { @@ -488,7 +503,7 @@ app.post('/problem/:id/import', async (req, res) => { app.get('/problem/:id/manage', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -512,7 +527,7 @@ app.get('/problem/:id/manage', async (req, res) => { app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -560,7 +575,7 @@ app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount: async function setPublic(req, res, is_public) { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); let allowedManage = await problem.isAllowedManageBy(res.locals.user); @@ -571,10 +586,7 @@ async function setPublic(req, res, is_public) { problem.publicize_time = new Date(); await problem.save(); - JudgeState.model.update( - { is_public: is_public }, - { where: { problem_id: id } } - ); + JudgeState.query('UPDATE `judge_state` SET `is_public` = ' + is_public + ' WHERE `problem_id` = ' + id); res.redirect(syzoj.utils.makeUrl(['problem', id])); } catch (e) { @@ -596,7 +608,7 @@ app.post('/problem/:id/dis_public', async (req, res) => { app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); const curUser = res.locals.user; if (!problem) throw new ErrorMessage('无此题目。'); @@ -625,6 +637,9 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 if (!file.md5) throw new ErrorMessage('上传答案文件失败。'); judge_state = await JudgeState.create({ + submit_time: parseInt((new Date()).getTime() / 1000), + status: 'Unknown', + task_id: randomstring.generate(10), code: file.md5, code_length: size, language: null, @@ -636,14 +651,16 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 let code; if (req.files['answer']) { if (req.files['answer'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码文件太大。'); - let fs = Promise.promisifyAll(require('fs')); - code = (await fs.readFileAsync(req.files['answer'][0].path)).toString(); + code = (await fs.readFile(req.files['answer'][0].path)).toString(); } else { if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码太长。'); code = req.body.code; } judge_state = await JudgeState.create({ + submit_time: parseInt((new Date()).getTime() / 1000), + status: 'Unknown', + task_id: randomstring.generate(10), code: code, code_length: code.length, language: req.body.language, @@ -656,7 +673,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 let contest_id = parseInt(req.query.contest_id); let contest; if (contest_id) { - contest = await Contest.fromID(contest_id); + contest = await Contest.findById(contest_id); if (!contest) throw new ErrorMessage('无此比赛。'); if ((!contest.isRunning()) && (!await contest.isSupervisior(curUser))) throw new ErrorMessage('比赛未开始或已结束。'); let problems_id = await contest.getProblems(); @@ -721,7 +738,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 app.post('/problem/:id/delete', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!problem.isAllowedManageBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -740,7 +757,7 @@ app.post('/problem/:id/delete', async (req, res) => { app.get('/problem/:id/testdata', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -767,7 +784,7 @@ app.get('/problem/:id/testdata', async (req, res) => { app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -790,7 +807,7 @@ app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, r app.post('/problem/:id/testdata/delete/:filename', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -809,7 +826,7 @@ app.post('/problem/:id/testdata/delete/:filename', async (req, res) => { app.get('/problem/:id/testdata/download/:filename?', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -836,14 +853,14 @@ app.get('/problem/:id/testdata/download/:filename?', async (req, res) => { app.get('/problem/:id/download/additional_file', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); // XXX: Reduce duplication (see the '/problem/:id/submit' handler) let contest_id = parseInt(req.query.contest_id); if (contest_id) { - let contest = await Contest.fromID(contest_id); + let contest = await Contest.findById(contest_id); if (!contest) throw new ErrorMessage('无此比赛。'); if (!contest.isRunning()) throw new ErrorMessage('比赛未开始或已结束。'); let problems_id = await contest.getProblems(); @@ -869,7 +886,7 @@ app.get('/problem/:id/download/additional_file', async (req, res) => { app.get('/problem/:id/statistics/:type', async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); @@ -899,7 +916,7 @@ app.get('/problem/:id/statistics/:type', async (req, res) => { app.post('/problem/:id/custom-test', app.multer.fields([{ name: 'code_upload', maxCount: 1 }, { name: 'input_file', maxCount: 1 }]), async (req, res) => { try { let id = parseInt(req.params.id); - let problem = await Problem.fromID(id); + let problem = await Problem.findById(id); if (!problem) throw new ErrorMessage('无此题目。'); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) }); diff --git a/modules/problem_tag.js b/modules/problem_tag.js index f6b0f54..6b90f96 100644 --- a/modules/problem_tag.js +++ b/modules/problem_tag.js @@ -5,7 +5,7 @@ app.get('/problems/tag/:id/edit', async (req, res) => { if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem_tag')) throw new ErrorMessage('您没有权限进行此操作。'); let id = parseInt(req.params.id) || 0; - let tag = await ProblemTag.fromID(id); + let tag = await ProblemTag.findById(id); if (!tag) { tag = await ProblemTag.create(); @@ -28,7 +28,7 @@ app.post('/problems/tag/:id/edit', async (req, res) => { if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem_tag')) throw new ErrorMessage('您没有权限进行此操作。'); let id = parseInt(req.params.id) || 0; - let tag = await ProblemTag.fromID(id); + let tag = await ProblemTag.findById(id); if (!tag) { tag = await ProblemTag.create(); diff --git a/modules/submission.js b/modules/submission.js index 367d2c1..57cc9dd 100644 --- a/modules/submission.js +++ b/modules/submission.js @@ -23,23 +23,32 @@ const displayConfig = { app.get('/submissions', async (req, res) => { try { const curUser = res.locals.user; - let user = await User.fromName(req.query.submitter || ''); - let where = {}; + + let query = JudgeState.createQueryBuilder(); + let isFiltered = false; + let inContest = false; - if (user) where.user_id = user.id; - else if (req.query.submitter) where.user_id = -1; + + let user = await User.fromName(req.query.submitter || ''); + if (user) { + query.andWhere('user_id = :user_id', { user_id: user.id }); + isFiltered = true; + } else if (req.query.submitter) { + query.andWhere('user_id = :user_id', { user_id: 0 }); + isFiltered = true; + } if (!req.query.contest) { - where.type = { $eq: 0 }; + query.andWhere('type = 0'); } else { const contestId = Number(req.query.contest); - const contest = await Contest.fromID(contestId); + const contest = await Contest.findById(contestId); contest.ended = contest.isEnded(); if ((contest.ended && contest.is_public) || // If the contest is ended and is not hidden (curUser && await contest.isSupervisior(curUser)) // Or if the user have the permission to check ) { - where.type = { $eq: 1 }; - where.type_info = { $eq: contestId }; + query.andWhere('type = 1'); + query.andWhere('type_info = :type_info', { type_info: contestId }); inContest = true; } else { throw new Error("您暂时无权查看此比赛的详细评测信息。"); @@ -47,56 +56,55 @@ app.get('/submissions', async (req, res) => { } let minScore = parseInt(req.query.min_score); + if (!isNaN(minScore)) query.andWhere('score >= :minScore', { minScore }); let maxScore = parseInt(req.query.max_score); + if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore }); - if (!isNaN(minScore) || !isNaN(maxScore)) { - if (isNaN(minScore)) minScore = 0; - if (isNaN(maxScore)) maxScore = 100; - if (!(minScore === 0 && maxScore === 100)) { - where.score = { - $and: { - $gte: parseInt(minScore), - $lte: parseInt(maxScore) - } - }; + if (!isNaN(minScore) || !isNaN(maxScore)) isFiltered = true; + + if (req.query.language) { + if (req.query.language === 'submit-answer') { + query.andWhere(new TypeORM.Brackets(qb => { + qb.orWhere('language = :language', { language: '' }) + .orWhere('language IS NULL'); + })); + isFiltered = true; + } else if (req.query.language === 'non-submit-answer') { + query.andWhere('language != :language', { language: '' }) + .andWhere('language IS NOT NULL'); + isFiltered = true; + } else { + query.andWhere('language = :language', { language: req.query.language }); } } - if (req.query.language) { - if (req.query.language === 'submit-answer') where.language = { $or: [{ $eq: '', }, { $eq: null }] }; - else if (req.query.language === 'non-submit-answer') where.language = { $not: '' }; - else where.language = req.query.language; + if (req.query.status) { + query.andWhere('status LIKE :status', { status: req.query.status + '%' }); + isFiltered = true; } - if (req.query.status) where.status = { $like: req.query.status + '%' }; if (!inContest && (!curUser || !await curUser.hasPrivilege('manage_problem'))) { if (req.query.problem_id) { let problem_id = parseInt(req.query.problem_id); - let problem = await Problem.fromID(problem_id); + let problem = await Problem.findById(problem_id); if (!problem) throw new ErrorMessage("无此题目。"); if (await problem.isAllowedUseBy(res.locals.user)) { - where.problem_id = { - $and: [ - { $eq: where.problem_id = problem_id } - ] - }; + query.andWhere('problem_id = :problem_id', { problem_id: parseInt(req.query.problem_id) || 0 }); + isFiltered = true; } else { throw new ErrorMessage("您没有权限进行此操作。"); } } else { - where.is_public = { - $eq: true, - }; + query.andWhere('is_public = true'); } - } else { - if (req.query.problem_id) where.problem_id = parseInt(req.query.problem_id) || -1; + } else if (req.query.problem_id) { + query.andWhere('problem_id = :problem_id', { problem_id: parseInt(req.query.problem_id) || 0 }); + isFiltered = true; } - let isFiltered = !!(where.problem_id || where.user_id || where.score || where.language || where.status); - - 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, [['id', 'desc']], true); + let paginate = syzoj.utils.paginate(await JudgeState.countQuery(query), req.query.page, syzoj.config.page.judge_state); + let judge_state = await JudgeState.queryPage(paginate, query, { id: "DESC" }); await judge_state.forEachAsync(async obj => { await obj.loadRelationships(); @@ -132,14 +140,14 @@ app.get('/submissions', async (req, res) => { app.get('/submission/:id', async (req, res) => { try { const id = parseInt(req.params.id); - const judge = await JudgeState.fromID(id); + const judge = await JudgeState.findById(id); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。"); const curUser = res.locals.user; if (!await judge.isAllowedVisitBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。'); let contest; if (judge.type === 1) { - contest = await Contest.fromID(judge.type_info); + contest = await Contest.findById(judge.type_info); contest.ended = contest.isEnded(); if ((!contest.ended || !contest.is_public) && @@ -194,7 +202,7 @@ app.get('/submission/:id', async (req, res) => { app.post('/submission/:id/rejudge', async (req, res) => { try { let id = parseInt(req.params.id); - let judge = await JudgeState.fromID(id); + let judge = await JudgeState.findById(id); if (judge.pending && !(res.locals.user && await res.locals.user.hasPrivilege('manage_problem'))) throw new ErrorMessage('无法重新评测一个评测中的提交。'); diff --git a/modules/user.js b/modules/user.js index 15de195..aad8f5a 100644 --- a/modules/user.js +++ b/modules/user.js @@ -13,7 +13,7 @@ app.get('/ranklist', async (req, res) => { throw new ErrorMessage('错误的排序参数。'); } 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 }, [[sort, order]]); + let ranklist = await User.queryPage(paginate, { is_show: true }, { [sort]: order.toUpperCase() }); await ranklist.forEachAsync(async x => x.renderInformation()); res.render('ranklist', { @@ -76,7 +76,7 @@ app.post('/logout', async (req, res) => { app.get('/user/:id', async (req, res) => { try { let id = parseInt(req.params.id); - let user = await User.fromID(id); + let user = await User.findById(id); if (!user) throw new ErrorMessage('无此用户。'); user.ac_problems = await user.getACProblems(); user.articles = await user.getArticles(); @@ -86,7 +86,10 @@ app.get('/user/:id', async (req, res) => { await user.renderInformation(); user.emailVisible = user.public_email || user.allowedEdit; - const ratingHistoryValues = await RatingHistory.query(null, { user_id: user.id }, [['rating_calculation_id', 'asc']]); + const ratingHistoryValues = await RatingHistory.find({ + where: { user_id: user.id }, + order: { rating_calculation_id: 'ASC' } + }); const ratingHistories = [{ contestName: "初始积分", value: syzoj.config.default.user.rating, @@ -95,7 +98,7 @@ app.get('/user/:id', async (req, res) => { }]; for (const history of ratingHistoryValues) { - const contest = await Contest.fromID((await RatingCalculation.fromID(history.rating_calculation_id)).contest_id); + const contest = await Contest.findById((await RatingCalculation.findById(history.rating_calculation_id)).contest_id); ratingHistories.push({ contestName: contest.title, value: history.rating_after, @@ -122,7 +125,7 @@ app.get('/user/:id', async (req, res) => { app.get('/user/:id/edit', async (req, res) => { try { let id = parseInt(req.params.id); - let user = await User.fromID(id); + let user = await User.findById(id); if (!user) throw new ErrorMessage('无此用户。'); let allowedEdit = await user.isAllowedEditBy(res.locals.user); @@ -156,7 +159,7 @@ app.post('/user/:id/edit', async (req, res) => { let user; try { let id = parseInt(req.params.id); - user = await User.fromID(id); + user = await User.findById(id); if (!user) throw new ErrorMessage('无此用户。'); let allowedEdit = await user.isAllowedEditBy(res.locals.user); diff --git a/package.json b/package.json index b175509..a25eb0a 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "An OnlineJudge System for OI", "main": "app.js", "scripts": { + "prepublish": "tsc -p .", "start": "node app.js", "test": "echo \"Error: no test specified\" && exit 1" }, @@ -23,8 +24,10 @@ }, "homepage": "https://github.com/syzoj/syzoj#readme", "dependencies": { + "@types/fs-extra": "^5.0.5", "ansi-to-html": "^0.6.10", "async-lock": "^1.2.0", + "bluebird": "^3.5.4", "body-parser": "^1.15.2", "cheerio": "^1.0.0-rc.1", "command-line-args": "^5.1.0", @@ -46,16 +49,17 @@ "moment": "^2.24.0", "msgpack-lite": "^0.1.26", "multer": "^1.2.0", + "mysql2": "^1.6.5", "node-7z": "^0.4.0", "nodemailer": "^4.7.0", "object-assign-deep": "^0.4.0", "object-hash": "^1.3.1", "randomstring": "^1.1.5", "redis": "^2.8.0", + "reflect-metadata": "^0.1.13", "request": "^2.74.0", "request-promise": "^4.2.4", "sendmail": "^1.1.1", - "sequelize": "^5.1.1", "serialize-javascript": "^1.6.1", "session-file-store": "^1.0.0", "socket.io": "^2.2.0", @@ -64,6 +68,8 @@ "syzoj-renderer": "^1.0.5", "tempfile": "^2.0.0", "tmp-promise": "^1.0.3", + "typeorm": "^0.2.16", + "typescript": "^3.4.3", "uuid": "^3.3.2", "waliyun": "^3.1.1", "winston": "^3.2.1", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4ecad07 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "preserveConstEnums": true, + "sourceMap": true, + "outDir": "./models-built", + "rootDir": "./models", + "emitDecoratorMetadata": true, + "experimentalDecorators": true + } +} diff --git a/yarn.lock b/yarn.lock index f94b9d7..b60bf1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,13 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@types/fs-extra@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" + integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== + dependencies: + "@types/node" "*" + "@types/node@*": version "11.11.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.4.tgz#8808bd5a82bbf6f5d412eff1c228d178e7c24bb3" @@ -83,11 +90,28 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + ansi-styles@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-to-html@^0.6.10: version "0.6.10" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.10.tgz#412114353bac2589a034db7ec5b371b8ba771131" @@ -95,11 +119,16 @@ ansi-to-html@^0.6.10: dependencies: entities "^1.1.1" -any-promise@^1.3.0: +any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= +app-root-path@^2.0.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" + integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== + append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -213,6 +242,11 @@ bagpipe@^0.3.5: resolved "https://registry.yarnpkg.com/bagpipe/-/bagpipe-0.3.5.tgz#e341d164fcb24cdf04ea7e05b765ec10c8aea6a1" integrity sha1-40HRZPyyTN8E6n4Ft2XsEMiupqE= +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -255,11 +289,16 @@ blob@0.0.5: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== -bluebird@^3.5.0, bluebird@^3.5.3: +bluebird@^3.5.0: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== +bluebird@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714" + integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw== + blueimp-md5@^2.3.0: version "2.10.0" resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.10.0.tgz#02f0843921f90dca14f5b8920a38593201d6964d" @@ -286,6 +325,14 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + browser-process-hrtime@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" @@ -324,7 +371,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer@^5.2.1: +buffer@^5.1.0, buffer@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== @@ -380,6 +427,11 @@ camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -406,6 +458,26 @@ chalk@^0.5.1: strip-ansi "^0.3.0" supports-color "^0.2.0" +chalk@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.3.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + cheerio@^1.0.0-rc.1: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -418,6 +490,17 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" +cli-highlight@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.0.tgz#1e2e6770b6c3d72c4c7d4e5ea27c496f82ec2c67" + integrity sha512-DxaFAFBGRaB+xueXP7jlJC5f867gZUZXz74RaxeZ9juEZM2Sm/s6ilzpz0uxKiT+Mj6TzHlibtXfG/dK5bSwDA== + dependencies: + chalk "^2.3.0" + highlight.js "^9.6.0" + mz "^2.4.0" + parse5 "^4.0.0" + yargs "^11.0.0" + cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -434,20 +517,12 @@ clone-response@1.0.2: dependencies: mimic-response "^1.0.0" -cls-bluebird@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cls-bluebird/-/cls-bluebird-2.1.0.tgz#37ef1e080a8ffb55c2f4164f536f1919e7968aee" - integrity sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4= - dependencies: - is-bluebird "^1.0.2" - shimmer "^1.1.0" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -color-convert@^1.9.1: +color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -542,6 +617,11 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" @@ -619,6 +699,17 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -688,7 +779,7 @@ debug@~3.1.0: dependencies: ms "2.0.0" -decamelize@^1.1.1: +decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -850,10 +941,10 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -dottie@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.1.tgz#697ad9d72004db7574d21f892466a3c285893659" - integrity sha512-ch5OQgvGDK2u8pSZeSYAQaV/lczImd7pMJ7BcEPXmnFVjy4yJIzP6CsODJUTH8mg1tyH1Z2abOiuJO3DjZ/GBw== +dotenv@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" + integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== double-ended-queue@^2.1.0-0: version "2.1.0-0" @@ -913,6 +1004,11 @@ email-validator@^2.0.3: resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + enabled@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" @@ -925,7 +1021,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== @@ -987,7 +1083,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2: +escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -1047,6 +1143,19 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + express-session@^1.14.1: version "1.15.6" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a" @@ -1160,6 +1269,11 @@ fecha@^2.3.3: resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== +figlet@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.1.tgz#48d35df9d9b10b1b3888302e6e57904a0b00509c" + integrity sha512-qc8gycfnnfOmfvPl7Fi3JeTbcvdmbZkckyUVGGAM02je7Ookvu+bBfKy1I4FKqTsQHCs3ARJ76ip/k98r+OQuQ== + file-size@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-size/-/file-size-1.0.0.tgz#3338267d5d206bbf60f4df60c19d7ed3813a4657" @@ -1231,6 +1345,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -1286,11 +1407,28 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-proxy@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" @@ -1311,6 +1449,13 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1318,6 +1463,18 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + got@^8.3.1: version "8.3.2" resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" @@ -1381,6 +1538,13 @@ has-ansi@^0.1.0: dependencies: ansi-regex "^0.2.0" +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -1393,6 +1557,11 @@ has-cors@1.1.0: resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + has-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" @@ -1405,6 +1574,11 @@ has-to-string-tag-x@^1.2.0: dependencies: has-symbol-support-x "^1.4.1" +highlight.js@^9.6.0: + version "9.15.6" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.6.tgz#72d4d8d779ec066af9a17cb14360c3def0aa57c4" + integrity sha512-zozTAWM1D6sozHo8kqhfYgsac+B+q0PmsjXeyDrYIHHcBN0zTVT66+s2GW1GZv7DbyaROdLXKdabwS/WqPyIdQ== + html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -1487,12 +1661,15 @@ indexof@0.0.1: resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= -inflection@1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" - integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY= +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" -inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -1520,6 +1697,11 @@ invert-kv@^1.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + ipaddr.js@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" @@ -1530,11 +1712,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bluebird@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bluebird/-/is-bluebird-1.0.2.tgz#096439060f4aa411abee19143a84d6a55346d6e2" - integrity sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI= - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -1562,6 +1739,11 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + is-retry-allowed@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" @@ -1615,6 +1797,14 @@ javascript-time-ago@^1.0.30: resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-1.0.35.tgz#737f12e8cfec3de660b91a64414b1ec23b8ecf04" integrity sha512-iXkW8Q39iHJMLKqhEQw+So3PC6lZpd+ldedtqWL/lXW+pjn50eJm5R8boPgMmT0FGwSAtnhuPc/l8aGSRxiBDQ== +js-yaml@^3.12.2: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.0: version "3.13.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" @@ -1790,6 +1980,13 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -1846,6 +2043,14 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -1922,7 +2127,7 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== -lru-cache@^4.0.1: +lru-cache@^4.0.1, lru-cache@^4.1.3: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -1945,6 +2150,13 @@ make-dir@^1.0.0, make-dir@^1.2.0: dependencies: pify "^3.0.0" +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + mariadb@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/mariadb/-/mariadb-2.0.3.tgz#d265813e95579795d6c1e5dcfd034fef873d6740" @@ -1979,7 +2191,7 @@ markdown-it@^8.4.2: mdurl "^1.0.1" uc.micro "^1.0.5" -"mathjax-node-page@github:Menci/mathjax-node-page#1a9093ddd4925ab42e96fb9fdd566db37e5f3597": +mathjax-node-page@Menci/mathjax-node-page.git#1a9093ddd4925ab42e96fb9fdd566db37e5f3597: version "3.0.2" resolved "https://codeload.github.com/Menci/mathjax-node-page/tar.gz/1a9093ddd4925ab42e96fb9fdd566db37e5f3597" dependencies: @@ -2017,6 +2229,15 @@ mem@^1.1.0: dependencies: mimic-fn "^1.0.0" +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -2049,11 +2270,23 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -2071,14 +2304,7 @@ mkdirp@^0.5.1: dependencies: minimist "0.0.8" -moment-timezone@^0.5.21: - version "0.5.23" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" - integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0", moment@^2.24.0: +moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -2117,11 +2343,46 @@ multer@^1.2.0: type-is "^1.6.4" xtend "^4.0.0" +mysql2@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-1.6.5.tgz#6695304fa2ce793dda5c98e8bbec65cbd2e6cb9d" + integrity sha512-zedaOOyb3msuuZcJJnxIX/EGOpmljDG7B+UevRH5lqcv+yhy9eCwkArBz8/AO+/rlY3/oCsOdG8R5oD6k0hNfg== + dependencies: + denque "^1.4.0" + generate-function "^2.3.1" + iconv-lite "^0.4.24" + long "^4.0.0" + lru-cache "^4.1.3" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.1" + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +named-placeholders@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" + integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== + dependencies: + lru-cache "^4.1.3" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + node-7z@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-7z/-/node-7z-0.4.0.tgz#390124c580183cabd388d137f30b239b44bd59a8" @@ -2225,7 +2486,7 @@ on-headers@~1.0.1: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -2258,6 +2519,15 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" +os-locale@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -2268,6 +2538,11 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + p-event@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" @@ -2285,6 +2560,11 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -2292,6 +2572,13 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -2299,6 +2586,13 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + p-timeout@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" @@ -2311,7 +2605,17 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= -parse5@4.0.0: +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-require@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" + integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc= + +parse5@4.0.0, parse5@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== @@ -2352,7 +2656,12 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-key@^2.0.0: +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= @@ -2442,6 +2751,14 @@ psl@^1.1.24, psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -2559,6 +2876,11 @@ redis@^2.8.0: redis-commands "^1.2.0" redis-parser "^2.6.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" @@ -2626,6 +2948,11 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -2633,13 +2960,6 @@ responselike@1.0.2: dependencies: lowercase-keys "^1.0.0" -retry-as-promised@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-3.2.0.tgz#769f63d536bec4783549db0777cb56dadd9d8543" - integrity sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg== - dependencies: - any-promise "^1.3.0" - retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" @@ -2655,7 +2975,7 @@ safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, s resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: +sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -2674,6 +2994,11 @@ seek-bzip@^1.0.5: dependencies: commander "~2.8.1" +semver@^5.5.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== + semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -2706,33 +3031,10 @@ sendmail@^1.1.1: dkim-signer "^0.2.2" mailcomposer "^3.12.0" -sequelize-pool@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-1.0.2.tgz#89c767882bbdb8a41dac66922ed9820939a5401e" - integrity sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w== - dependencies: - bluebird "^3.5.3" - -sequelize@^5.1.1: - version "5.2.7" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.2.7.tgz#48a53f7276e7fbee522f123b3bfa0d89632d13d3" - integrity sha512-+GCoERQT9ForzSkr6Dfukw3kbuwLY0KErwvaxpthHorjDUjJi/Ebaye58qcXCuz105UuY5EP66fSChwd3UAl3A== - dependencies: - bluebird "^3.5.0" - cls-bluebird "^2.1.0" - debug "^4.1.1" - dottie "^2.0.0" - inflection "1.12.0" - lodash "^4.17.11" - moment "^2.24.0" - moment-timezone "^0.5.21" - retry-as-promised "^3.1.0" - semver "^5.6.0" - sequelize-pool "^1.0.2" - toposort-class "^1.0.1" - uuid "^3.2.1" - validator "^10.11.0" - wkx "^0.4.6" +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= serialize-javascript@^1.6.1: version "1.6.1" @@ -2782,11 +3084,6 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shimmer@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -2881,6 +3178,11 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sqlstring@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40" + integrity sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A= + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -2950,6 +3252,15 @@ string-width@^2.0.0, string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + string_decoder@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" @@ -2990,6 +3301,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + strip-dirs@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" @@ -3014,6 +3332,18 @@ supports-color@^0.2.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -3075,6 +3405,20 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + dependencies: + any-promise "^1.0.0" + through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -3110,11 +3454,6 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== -toposort-class@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" - integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= - tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -3150,6 +3489,11 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -3182,6 +3526,31 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typeorm@^0.2.16: + version "0.2.16" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.16.tgz#cfc119254e21f37410a60616a726cad97a8d95f6" + integrity sha512-Ntx9Hjx2aJcPsbqTsqnhCZOG30bDQ8EalMa9J49CXMCatUuMbn9QTyreM9AuSQb2N91ENCRWPZIuzgRaSmC1Vw== + dependencies: + app-root-path "^2.0.1" + buffer "^5.1.0" + chalk "^2.4.2" + cli-highlight "^2.0.0" + debug "^4.1.1" + dotenv "^6.2.0" + glob "^7.1.2" + js-yaml "^3.12.2" + mkdirp "^0.5.1" + reflect-metadata "^0.1.13" + tslib "^1.9.0" + xml2js "^0.4.17" + yargonaut "^1.1.2" + yargs "^13.2.1" + +typescript@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f" + integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" @@ -3246,16 +3615,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -validator@^10.11.0: - version "10.11.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" - integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -3374,13 +3738,6 @@ winston@^3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" -wkx@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.6.tgz#228ab592e6457382ea6fb79fc825058d07fce523" - integrity sha512-LHxXlzRCYQXA9ZHgs8r7Gafh0gVOE8o3QmudM1PIkOdkXXjW7Thcl+gb2P2dRuKgW8cqkitCRZkkjtmWzpHi7A== - dependencies: - "@types/node" "*" - wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -3434,6 +3791,19 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml2js@^0.4.17: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + +xmlbuilder@~9.0.1: + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" + integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= + xmlchars@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-1.3.1.tgz#1dda035f833dbb4f86a0c28eaa6ca769214793cf" @@ -3462,11 +3832,33 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= +yargonaut@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c" + integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA== + dependencies: + chalk "^1.1.1" + figlet "^1.1.1" + parent-require "^1.0.0" + +yargs-parser@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b" + integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -3492,6 +3884,23 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" +yargs@^13.2.1: + version "13.2.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993" + integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA== + dependencies: + cliui "^4.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + os-locale "^3.1.0" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.0.0" + yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"