Browse Source

Move to TypeORM

master
Menci 6 years ago
parent
commit
56e5cd5315
  1. 1
      .gitignore
  2. 87
      app.js
  3. 4
      libs/judger.js
  4. 56
      models/article-comment.js
  5. 38
      models/article-comment.ts
  6. 91
      models/article.js
  7. 78
      models/article.ts
  8. 136
      models/common.js
  9. 63
      models/common.ts
  10. 142
      models/contest.ts
  11. 83
      models/contest_player.ts
  12. 48
      models/contest_ranklist.ts
  13. 81
      models/custom_test.js
  14. 71
      models/file.ts
  15. 31
      models/formatted_code.js
  16. 11
      models/formatted_code.ts
  17. 195
      models/judge_state.js
  18. 165
      models/judge_state.ts
  19. 277
      models/problem.ts
  20. 33
      models/problem_tag.js
  21. 15
      models/problem_tag.ts
  22. 37
      models/problem_tag_map.js
  23. 13
      models/problem_tag_map.ts
  24. 53
      models/rating_calculation.js
  25. 47
      models/rating_calculation.ts
  26. 43
      models/rating_history.js
  27. 27
      models/rating_history.ts
  28. 235
      models/user.js
  29. 205
      models/user.ts
  30. 37
      models/user_privilege.js
  31. 13
      models/user_privilege.ts
  32. 50
      models/waiting_judge.js
  33. 115
      modules/admin.js
  34. 14
      modules/api.js
  35. 38
      modules/api_v2.js
  36. 116
      modules/contest.js
  37. 36
      modules/discussion.js
  38. 17
      modules/index.js
  39. 203
      modules/problem.js
  40. 4
      modules/problem_tag.js
  41. 90
      modules/submission.js
  42. 15
      modules/user.js
  43. 8
      package.json
  44. 11
      tsconfig.json
  45. 609
      yarn.lock

1
.gitignore vendored

@ -3,6 +3,7 @@ config.json
*.db *.db
.git.* .git.*
package-lock.json package-lock.json
models-built
# Logs # Logs
logs logs

87
app.js

@ -12,6 +12,10 @@ const optionDefinitions = [
const options = commandLineArgs(optionDefinitions); const options = commandLineArgs(optionDefinitions);
require('reflect-metadata');
global.Promise = require('bluebird');
global.syzoj = { global.syzoj = {
rootDir: __dirname, rootDir: __dirname,
config: require('object-assign-deep')({}, require('./config-example.json'), require(options.config)), config: require('object-assign-deep')({}, require('./config-example.json'), require(options.config)),
@ -121,56 +125,26 @@ global.syzoj = {
}); });
}, },
async connectDatabase() { async connectDatabase() {
let Sequelize = require('sequelize'); const TypeORM = require('typeorm');
let Op = Sequelize.Op; global.TypeORM = TypeORM;
let operatorsAliases = {
$eq: Op.eq, const modelsPath = __dirname + '/models/';
$ne: Op.ne, const modelsBuiltPath = __dirname + '/models-built/';
$gte: Op.gte, const models = fs.readdirSync(modelsPath)
$gt: Op.gt, .filter(filename => filename.endsWith('.ts') && filename !== 'common.ts')
$lte: Op.lte, .map(filename => require(modelsBuiltPath + filename.replace('.ts', '.js')).default);
$lt: Op.lt,
$not: Op.not, const connection = await TypeORM.createConnection({
$in: Op.in, type: 'mariadb',
$notIn: Op.notIn, host: this.config.db.host.split(':')[0],
$is: Op.is, port: this.config.db.host.split(':')[1] || 3306,
$like: Op.like, username: this.config.db.username,
$notLike: Op.notLike, password: this.config.db.password,
$iLike: Op.iLike, database: this.config.db.database,
$notILike: Op.notILike, entities: models,
$regexp: Op.regexp, synchronize: true,
$notRegexp: Op.notRegexp, logging: false
$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
};
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
}); });
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() { loadModules() {
fs.readdir('./modules/', (err, files) => { fs.readdir('./modules/', (err, files) => {
@ -182,22 +156,11 @@ global.syzoj = {
.forEach((file) => this.modules.push(require(`./modules/${file}`))); .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) { lib(name) {
return require(`./libs/${name}`); return require(`./libs/${name}`);
}, },
model(name) { model(name) {
return require(`./models/${name}`); return require(`./models-built/${name}`).default;
}, },
loadHooks() { loadHooks() {
let Session = require('express-session'); let Session = require('express-session');
@ -221,7 +184,7 @@ global.syzoj = {
let User = syzoj.model('user'); let User = syzoj.model('user');
if (req.session.user_id) { 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; res.locals.user = user;
next(); next();
}).catch((err) => { }).catch((err) => {

4
libs/judger.js

@ -2,7 +2,7 @@ const enums = require('./enums');
const util = require('util'); const util = require('util');
const winston = require('winston'); const winston = require('winston');
const msgPack = require('msgpack-lite'); const msgPack = require('msgpack-lite');
const fs = Promise.promisifyAll(require('fs-extra')); const fs = require('fs-extra');
const interface = require('./judger_interfaces'); const interface = require('./judger_interfaces');
const judgeResult = require('./judgeResult'); const judgeResult = require('./judgeResult');
@ -198,7 +198,7 @@ module.exports.judge = async function (judge_state, problem, priority) {
case 'submit-answer': case 'submit-answer':
type = enums.ProblemType.AnswerSubmission; type = enums.ProblemType.AnswerSubmission;
param = null; 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; break;
case 'interaction': case 'interaction':
type = enums.ProblemType.Interaction; type = enums.ProblemType.Interaction;

56
models/article-comment.js

@ -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;

38
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);
}
};

91
models/article.js

@ -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;

78
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();
});
}
};

136
models/common.js

@ -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;

63
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<T extends TypeORM.BaseEntity>(this: TypeORM.ObjectType<T>, id?: number): Promise<T | undefined> {
return await (this as any).findOne(parseInt(id as any) || 0);
}
async destroy() {
await TypeORM.getManager().remove(this);
}
static async countQuery<T extends TypeORM.BaseEntity>(this: TypeORM.ObjectType<T>, query: TypeORM.SelectQueryBuilder<T> | 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();
}
}

142
models/contest.js → models/contest.ts

@ -1,77 +1,69 @@
let Sequelize = require('sequelize'); import * as TypeORM from "typeorm";
let db = syzoj.db; import Model from "./common";
let User = syzoj.model('user'); declare var syzoj, ErrorMessage: any;
let Problem = syzoj.model('problem');
let ContestRanklist = syzoj.model('contest_ranklist'); import User from "./user";
let ContestPlayer = syzoj.model('contest_player'); import Problem from "./problem";
import ContestRanklist from "./contest_ranklist";
let model = db.define('contest', { import ContestPlayer from "./contest_player";
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: Sequelize.STRING(80) }, enum ContestType {
subtitle: { type: Sequelize.TEXT }, NOI = "noi",
start_time: { type: Sequelize.INTEGER }, IOI = "ioi",
end_time: { type: Sequelize.INTEGER }, ICPC = "acm"
}
holder_id: {
type: Sequelize.INTEGER, @TypeORM.Entity()
references: { export default class Contest extends Model {
model: 'user', @TypeORM.PrimaryGeneratedColumn()
key: 'id' 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: noi, ioi, acm
type: { type: Sequelize.STRING(10) }, @TypeORM.Column({ nullable: true, type: "enum", enum: ContestType })
type: ContestType;
information: { type: Sequelize.TEXT }, @TypeORM.Column({ nullable: true, type: "text" })
problems: { type: Sequelize.TEXT }, information: string;
admins: { type: Sequelize.TEXT },
ranklist_id: { @TypeORM.Column({ nullable: true, type: "text" })
type: Sequelize.INTEGER, problems: string;
references: {
model: 'contest_ranklist', @TypeORM.Column({ nullable: true, type: "text" })
key: 'id' admins: string;
}
}, @TypeORM.Index()
@TypeORM.Column({ nullable: true, type: "integer" })
is_public: { type: Sequelize.BOOLEAN }, ranklist_id: number;
hide_statistics: { type: Sequelize.BOOLEAN }
}, { @TypeORM.Column({ nullable: true, type: "boolean" })
timestamps: false, is_public: boolean;
tableName: 'contest',
indexes: [ @TypeORM.Column({ nullable: true, type: "boolean" })
{ hide_statistics: boolean;
fields: ['holder_id'],
}, holder?: User;
{ ranklist?: ContestRanklist;
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)));
}
async loadRelationships() { async loadRelationships() {
this.holder = await User.fromID(this.holder_id); this.holder = await User.findById(this.holder_id);
this.ranklist = await ContestRanklist.fromID(this.ranklist_id); this.ranklist = await ContestRanklist.findById(this.ranklist_id);
} }
async isSupervisior(user) { async isSupervisior(user) {
@ -110,7 +102,7 @@ class Contest extends Model {
async setProblems(s) { async setProblems(s) {
let a = []; let a = [];
await s.split('|').forEachAsync(async x => { await s.split('|').forEachAsync(async x => {
let problem = await Problem.fromID(x); let problem = await Problem.findById(x);
if (!problem) return; if (!problem) return;
a.push(x); a.push(x);
}); });
@ -146,19 +138,13 @@ class Contest extends Model {
}); });
} }
isRunning(now) { isRunning(now?) {
if (!now) now = syzoj.utils.getCurrentDate(); if (!now) now = syzoj.utils.getCurrentDate();
return now >= this.start_time && now < this.end_time; return now >= this.start_time && now < this.end_time;
} }
isEnded(now) { isEnded(now?) {
if (!now) now = syzoj.utils.getCurrentDate(); if (!now) now = syzoj.utils.getCurrentDate();
return now >= this.end_time; return now >= this.end_time;
} }
getModel() { return model; }
} }
Contest.model = model;
module.exports = Contest;

83
models/contest_player.js → models/contest_player.ts

@ -1,50 +1,41 @@
let Sequelize = require('sequelize'); import * as TypeORM from "typeorm";
let db = syzoj.db; import Model from "./common";
let User = syzoj.model('user'); import User from "./user";
let Problem = syzoj.model('problem'); import Contest from "./contest";
let model = db.define('contest_player', { @TypeORM.Entity()
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, export default class ContestPlayer extends Model {
contest_id: { type: Sequelize.INTEGER }, @TypeORM.PrimaryGeneratedColumn()
user_id: { type: Sequelize.INTEGER }, id: number;
score: { type: Sequelize.INTEGER }, @TypeORM.Index()
score_details: { type: Sequelize.JSON }, @TypeORM.Column({ nullable: true, type: "integer" })
time_spent: { type: Sequelize.INTEGER } contest_id: number;
}, {
timestamps: false, @TypeORM.Index()
tableName: 'contest_player', @TypeORM.Column({ nullable: true, type: "integer" })
indexes: [ user_id: number;
{
fields: ['contest_id'], @TypeORM.Column({ nullable: true, type: "integer" })
}, score: number;
{
fields: ['user_id'], @TypeORM.Column({ nullable: true, type: "json" })
} score_details: object;
]
}); @TypeORM.Column({ nullable: true, type: "integer" })
time_spent: number;
let Model = require('./common');
class ContestPlayer extends Model { user?: User;
static async create(val) { contest?: Contest;
return ContestPlayer.fromRecord(ContestPlayer.model.build(Object.assign({
contest_id: 0,
user_id: 0,
score: 0,
score_details: {},
time_spent: 0
}, val)));
}
static async findInContest(where) { static async findInContest(where) {
return ContestPlayer.findOne({ where: where }); return ContestPlayer.findOne({ where: where });
} }
async loadRelationships() { async loadRelationships() {
let Contest = syzoj.model('contest'); this.user = await User.findById(this.user_id);
this.user = await User.fromID(this.user_id); this.contest = await Contest.findById(this.contest_id);
this.contest = await Contest.fromID(this.contest_id);
} }
async updateScore(judge_state) { async updateScore(judge_state) {
@ -65,7 +56,7 @@ class ContestPlayer extends Model {
time: judge_state.submit_time 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); arr.sort((a, b) => a.time - b.time);
let maxScoreSubmission = null; let maxScoreSubmission = null;
@ -117,7 +108,7 @@ class ContestPlayer extends Model {
time: judge_state.submit_time 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); arr.sort((a, b) => a.time - b.time);
this.score_details[judge_state.problem_id].unacceptedCount = 0; 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;

48
models/contest_ranklist.js → models/contest_ranklist.ts

@ -1,32 +1,26 @@
let Sequelize = require('sequelize'); import * as TypeORM from "typeorm";
let db = syzoj.db; import Model from "./common";
let User = syzoj.model('user'); declare var syzoj: any;
let Problem = syzoj.model('problem');
let ContestPlayer = syzoj.model('contest_player');
let model = db.define('contest_ranklist', { import ContestPlayer from "./contest_player";
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, import JudgeState from "./judge_state";
ranking_params: { type: Sequelize.JSON },
ranklist: { type: Sequelize.JSON }
}, {
timestamps: false,
tableName: 'contest_ranklist'
});
let Model = require('./common'); @TypeORM.Entity()
class ContestRanklist extends Model { export default class ContestRanklist extends Model {
static async create(val) { @TypeORM.PrimaryGeneratedColumn()
return ContestRanklist.fromRecord(ContestRanklist.model.build(Object.assign({ id: number;
ranking_params: {},
ranklist: {} @TypeORM.Column({ nullable: true, type: "json" })
}, val))); ranking_params: any;
}
@TypeORM.Column({ nullable: true, type: "json" })
ranklist: any;
async getPlayers() { async getPlayers() {
let a = []; let a = [];
for (let i = 1; i <= this.ranklist.player_num; i++) { 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; return a;
} }
@ -44,15 +38,13 @@ class ContestRanklist extends Model {
players.push(player); players.push(player);
} }
let JudgeState = syzoj.model('judge_state');
if (contest.type === 'noi' || contest.type === 'ioi') { if (contest.type === 'noi' || contest.type === 'ioi') {
for (let player of players) { for (let player of players) {
player.latest = 0; player.latest = 0;
player.score = 0; player.score = 0;
for (let i in player.score_details) { 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; if (!judge_state) continue;
player.latest = Math.max(player.latest, judge_state.submit_time); player.latest = Math.max(player.latest, judge_state.submit_time);
@ -94,10 +86,4 @@ class ContestRanklist extends Model {
this.ranklist = { player_num: players.length }; this.ranklist = { player_num: players.length };
for (let i = 0; i < players.length; i++) this.ranklist[i + 1] = players[i].id; for (let i = 0; i < players.length; i++) this.ranklist[i + 1] = players[i].id;
} }
getModel() { return model; }
} }
ContestRanklist.model = model;
module.exports = ContestRanklist;

81
models/custom_test.js

@ -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;

71
models/file.js → models/file.ts

@ -1,31 +1,24 @@
let Sequelize = require('sequelize'); import * as TypeORM from "typeorm";
let db = syzoj.db; import Model from "./common";
let model = db.define('file', { import * as fs from "fs-extra";
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
type: { type: Sequelize.STRING(80) }, declare var syzoj, ErrorMessage: any;
md5: { type: Sequelize.STRING(80), unique: true }
}, { @TypeORM.Entity()
timestamps: false, export default class File extends Model {
tableName: 'file', @TypeORM.PrimaryGeneratedColumn()
indexes: [ id: number;
{
fields: ['type'], @TypeORM.Index()
}, @TypeORM.Column({ nullable: true, type: "varchar", length: 80 })
{ type: string;
fields: ['md5'],
} @TypeORM.Index({ unique: true })
] @TypeORM.Column({ nullable: true, type: "varchar", length: 80 })
}); md5: string;
let Model = require('./common'); unzipSize?: number;
class File extends Model {
static create(val) {
return File.fromRecord(File.model.build(Object.assign({
type: '',
md5: ''
}, val)));
}
getPath() { getPath() {
return File.resolvePath(this.type, this.md5); return File.resolvePath(this.type, this.md5);
@ -54,24 +47,12 @@ class File extends Model {
} }
static async upload(path, type, noLimit) { static async upload(path, type, noLimit) {
let fs = Promise.promisifyAll(require('fs-extra')); let buf = await fs.readFile(path);
let buf = await fs.readFileAsync(path);
if (!noLimit && buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。'); 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); 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 } }); let file = await File.findOne({ where: { md5: key } });
if (!file) { if (!file) {
@ -101,10 +82,4 @@ class File extends Model {
if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。'); if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。');
else return this.unzipSize; else return this.unzipSize;
} }
getModel() { return model; }
} }
File.model = model;
module.exports = File;

31
models/formatted_code.js

@ -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;

11
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;
}

195
models/judge_state.js

@ -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;

165
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;
}
}

277
models/problem.js → models/problem.ts

@ -1,4 +1,4 @@
let statisticsStatements = { const statisticsStatements = {
fastest: fastest:
'\ '\
SELECT \ SELECT \
@ -176,109 +176,108 @@ ORDER BY `max_memory` DESC \
' '
}; };
let Sequelize = require('sequelize'); import * as TypeORM from "typeorm";
let db = syzoj.db; import Model from "./common";
let User = syzoj.model('user'); declare var syzoj, ErrorMessage: any;
let File = syzoj.model('file');
const fs = require('fs-extra');
const path = require('path');
let model = db.define('problem', { import User from "./user";
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, 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) }, import * as fs from "fs-extra";
user_id: { import * as path from "path";
type: Sequelize.INTEGER, import * as util from "util";
references: {
model: 'user',
key: 'id'
}
},
publicizer_id: {
type: Sequelize.INTEGER,
references: {
model: 'user',
key: 'id'
}
},
is_anonymous: { type: Sequelize.BOOLEAN },
description: { type: Sequelize.TEXT }, enum ProblemType {
input_format: { type: Sequelize.TEXT }, Traditional = "traditional",
output_format: { type: Sequelize.TEXT }, SubmitAnswer = "submit-answer",
example: { type: Sequelize.TEXT }, Interaction = "interaction"
limit_and_hint: { type: Sequelize.TEXT }, }
time_limit: { type: Sequelize.INTEGER }, @TypeORM.Entity()
memory_limit: { type: Sequelize.INTEGER }, 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 }, @TypeORM.Index()
submit_num: { type: Sequelize.INTEGER }, @TypeORM.Column({ nullable: true, type: "integer" })
is_public: { type: Sequelize.BOOLEAN }, user_id: number;
file_io: { type: Sequelize.BOOLEAN }, @TypeORM.Column({ nullable: true, type: "integer" })
file_io_input_name: { type: Sequelize.TEXT }, publicizer_id: number;
file_io_output_name: { type: Sequelize.TEXT },
publicize_time: { type: Sequelize.DATE }, @TypeORM.Column({ nullable: true, type: "boolean" })
is_anonymous: boolean;
type: { @TypeORM.Column({ nullable: true, type: "text" })
type: Sequelize.ENUM, description: string;
values: ['traditional', 'submit-answer', 'interaction']
} @TypeORM.Column({ nullable: true, type: "text" })
}, { input_format: string;
timestamps: false,
tableName: 'problem', @TypeORM.Column({ nullable: true, type: "text" })
indexes: [ output_format: string;
{
fields: ['title'], @TypeORM.Column({ nullable: true, type: "text" })
}, example: string;
{
fields: ['user_id'], @TypeORM.Column({ nullable: true, type: "text" })
}, limit_and_hint: string;
{
fields: ['publicize_time'], @TypeORM.Column({ nullable: true, type: "integer" })
}, time_limit: number;
]
}); @TypeORM.Column({ nullable: true, type: "integer" })
memory_limit: number;
let Model = require('./common');
class Problem extends Model { @TypeORM.Column({ nullable: true, type: "integer" })
static async create(val) { additional_file_id: number;
return Problem.fromRecord(Problem.model.build(Object.assign({
title: '', @TypeORM.Column({ nullable: true, type: "integer" })
user_id: '', ac_num: number;
publicizer_id: '',
is_anonymous: false, @TypeORM.Column({ nullable: true, type: "integer" })
description: '', submit_num: number;
input_format: '', @TypeORM.Index()
output_format: '', @TypeORM.Column({ nullable: true, type: "boolean" })
example: '', is_public: boolean;
limit_and_hint: '',
@TypeORM.Column({ nullable: true, type: "boolean" })
time_limit: syzoj.config.default.problem.time_limit, file_io: boolean;
memory_limit: syzoj.config.default.problem.memory_limit,
@TypeORM.Column({ nullable: true, type: "text" })
ac_num: 0, file_io_input_name: string;
submit_num: 0,
is_public: false, @TypeORM.Column({ nullable: true, type: "text" })
file_io_output_name: string;
file_io: false,
file_io_input_name: '', @TypeORM.Index()
file_io_output_name: '', @TypeORM.Column({ nullable: true, type: "datetime" })
publicize_time: Date;
type: 'traditional'
}, val))); @TypeORM.Column({ nullable: true,
} type: "enum",
enum: ProblemType,
default: ProblemType.Traditional
})
type: ProblemType;
user?: User;
publicizer?: User;
additional_file?: File;
async loadRelationships() { async loadRelationships() {
this.user = await User.fromID(this.user_id); this.user = await User.findById(this.user_id);
this.publicizer = await User.fromID(this.publicizer_id); this.publicizer = await User.findById(this.publicizer_id);
this.additional_file = await File.fromID(this.additional_file_id); this.additional_file = await File.findById(this.additional_file_id);
} }
async isAllowedEditBy(user) { async isAllowedEditBy(user) {
@ -324,7 +323,7 @@ class Problem extends Model {
await fs.remove(dir); await fs.remove(dir);
await fs.ensureDir(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 execFileAsync(__dirname + '/../bin/unzip', ['-j', '-o', '-d', dir, path]);
await fs.move(path, this.getTestdataArchivePath(), { overwrite: true }); 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 && 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 }); 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) {} try { await execFileAsync('dos2unix', [path.join(dir, filename)]); } catch (e) {}
await fs.remove(this.getTestdataArchivePath()); await fs.remove(this.getTestdataArchivePath());
@ -390,15 +389,15 @@ class Problem extends Model {
async listTestdata() { async listTestdata() {
try { try {
let dir = this.getTestdataPath(); let dir = this.getTestdataPath();
let list = await fs.readdir(dir); let filenameList = await fs.readdir(dir);
list = await list.mapAsync(async x => { let list = await Promise.all(filenameList.map(async x => {
let stat = await fs.stat(path.join(dir, x)); let stat = await fs.stat(path.join(dir, x));
if (!stat.isFile()) return undefined; if (!stat.isFile()) return undefined;
return { return {
filename: x, filename: x,
size: stat.size size: stat.size
}; };
}); }));
list = list.filter(x => x); list = list.filter(x => x);
@ -461,9 +460,8 @@ class Problem extends Model {
async getJudgeState(user, acFirst) { async getJudgeState(user, acFirst) {
if (!user) return null; if (!user) return null;
let JudgeState = syzoj.model('judge_state');
let where = { let where: any = {
user_id: user.id, user_id: user.id,
problem_id: this.id problem_id: this.id
}; };
@ -473,7 +471,9 @@ class Problem extends Model {
let state = await JudgeState.findOne({ let state = await JudgeState.findOne({
where: where, where: where,
order: [['submit_time', 'desc']] order: {
submit_time: 'DESC'
}
}); });
if (state) return state; if (state) return state;
@ -483,15 +483,16 @@ class Problem extends Model {
return await JudgeState.findOne({ return await JudgeState.findOne({
where: where, where: where,
order: [['submit_time', 'desc']] order: {
submit_time: 'DESC'
}
}); });
} }
async resetSubmissionCount() { async resetSubmissionCount() {
let JudgeState = syzoj.model('judge_state');
await syzoj.utils.lock(['Problem::resetSubmissionCount', this.id], async () => { await syzoj.utils.lock(['Problem::resetSubmissionCount', this.id], async () => {
this.submit_num = await JudgeState.count({ 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: { $not: 1 } }); this.ac_num = await JudgeState.count({ score: 100, problem_id: this.id, type: TypeORM.Not(1) });
await this.save(); await this.save();
}); });
} }
@ -501,12 +502,16 @@ class Problem extends Model {
let statement = statisticsStatements[type]; let statement = statisticsStatements[type];
if (!statement) return null; if (!statement) return null;
const entityManager = TypeORM.getManager();
statement = statement.replace('__PROBLEM_ID__', this.id); statement = statement.replace('__PROBLEM_ID__', this.id);
return await db.countQuery(statement); return JudgeState.countQuery(statement);
} }
// type: fastest / slowest / shortest / longest / earliest // type: fastest / slowest / shortest / longest / earliest
async getStatistics(type, paginate) { async getStatistics(type, paginate) {
const entityManager = TypeORM.getManager();
let statistics = { let statistics = {
type: type, type: type,
judge_state: null, judge_state: null,
@ -522,12 +527,11 @@ class Problem extends Model {
let a; let a;
if (!paginate.pageCnt) 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.findById(x.id));
statistics.judge_state = await a.mapAsync(async x => JudgeState.fromID(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 = []; let scoreCount = [];
for (let score of a) { for (let score of a) {
@ -557,14 +561,14 @@ class Problem extends Model {
} }
async getTags() { async getTags() {
let ProblemTagMap = syzoj.model('problem_tag_map'); let maps = await ProblemTagMap.find({
let maps = await ProblemTagMap.query(null, { where: {
problem_id: this.id problem_id: this.id
}
}); });
let ProblemTag = syzoj.model('problem_tag'); let res = await (maps as any).mapAsync(async map => {
let res = await maps.mapAsync(async map => { return ProblemTag.findById(map.tag_id);
return ProblemTag.fromID(map.tag_id);
}); });
res.sort((a, b) => { res.sort((a, b) => {
@ -575,8 +579,6 @@ class Problem extends Model {
} }
async setTags(newTagIDs) { async setTags(newTagIDs) {
let ProblemTagMap = syzoj.model('problem_tag_map');
let oldTagIDs = (await this.getTags()).map(x => x.id); let oldTagIDs = (await this.getTags()).map(x => x.id);
let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x)); let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x));
@ -604,14 +606,16 @@ class Problem extends Model {
} }
async changeID(id) { async changeID(id) {
const entityManager = TypeORM.getManager();
id = parseInt(id); id = parseInt(id);
await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); await entityManager.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 entityManager.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 entityManager.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 `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) { for (let contest of contests) {
let problemIDs = await contest.getProblems(); let problemIDs = await contest.getProblems();
@ -647,12 +651,17 @@ class Problem extends Model {
} }
async delete() { async delete() {
const entityManager = TypeORM.getManager();
let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath(); let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath();
await fs.remove(oldTestdataDir); await fs.remove(oldTestdataDir);
await fs.remove(oldTestdataZip); await fs.remove(oldTestdataZip);
let JudgeState = syzoj.model('judge_state'); let submissions = await JudgeState.find({
let submissions = await JudgeState.query(null, { problem_id: this.id }), submitCnt = {}, acUsers = new Set(); where: {
problem_id: this.id
}
}), submitCnt = {}, acUsers = new Set();
for (let sm of submissions) { for (let sm of submissions) {
if (sm.status === 'Accepted') acUsers.add(sm.user_id); if (sm.status === 'Accepted') acUsers.add(sm.user_id);
if (!submitCnt[sm.user_id]) { if (!submitCnt[sm.user_id]) {
@ -663,21 +672,15 @@ class Problem extends Model {
} }
for (let u in submitCnt) { for (let u in submitCnt) {
let user = await User.fromID(u); let user = await User.findById(parseInt(u));
user.submit_num -= submitCnt[u]; user.submit_num -= submitCnt[u];
if (acUsers.has(parseInt(u))) user.ac_num--; if (acUsers.has(parseInt(u))) user.ac_num--;
await user.save(); await user.save();
} }
await db.query('DELETE FROM `problem` WHERE `id` = ' + this.id); await entityManager.query('DELETE FROM `problem` WHERE `id` = ' + this.id);
await db.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id); await entityManager.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id);
await db.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id); await entityManager.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 `article` WHERE `problem_id` = ' + this.id);
} }
getModel() { return model; }
} }
Problem.model = model;
module.exports = Problem;

33
models/problem_tag.js

@ -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;

15
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;
}

37
models/problem_tag_map.js

@ -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;

13
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;
}

53
models/rating_calculation.js

@ -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;

47
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();
}
}

43
models/rating_history.js

@ -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;

27
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);
}
}

235
models/user.js

@ -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;

205
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" })
public_email: boolean;
@TypeORM.Column({ nullable: true, type: "boolean" })
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<User> {
return User.findOne({
where: {
email: email
}
});
}
static async fromName(name): Promise<User> {
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;
}
}

37
models/user_privilege.js

@ -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;

13
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;
}

50
models/waiting_judge.js

@ -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;

115
modules/admin.js

@ -9,14 +9,14 @@ const RatingHistory = syzoj.model('rating_history');
let ContestPlayer = syzoj.model('contest_player'); let ContestPlayer = syzoj.model('contest_player');
const calcRating = require('../libs/rating'); const calcRating = require('../libs/rating');
let db = syzoj.db;
app.get('/admin/info', async (req, res) => { app.get('/admin/info', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
let allSubmissionsCount = await JudgeState.count(); 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 problemsCount = await Problem.count();
let articlesCount = await Article.count(); let articlesCount = await Article.count();
let contestsCount = await Contest.count(); let contestsCount = await Contest.count();
@ -119,12 +119,12 @@ app.get('/admin/privilege', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
let a = await UserPrivilege.query(); let a = await UserPrivilege.find();
let users = {}; let users = {};
for (let p of a) { for (let p of a) {
if (!users[p.user_id]) { if (!users[p.user_id]) {
users[p.user_id] = { users[p.user_id] = {
user: await User.fromID(p.user_id), user: await User.findById(p.user_id),
privileges: [] privileges: []
}; };
} }
@ -149,7 +149,7 @@ app.post('/admin/privilege', async (req, res) => {
let data = JSON.parse(req.body.data); let data = JSON.parse(req.body.data);
for (let id in 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} 的用户。`); if (!user) throw new ErrorMessage(`不存在 ID 为 ${id} 的用户。`);
await user.setPrivileges(data[id]); await user.setPrivileges(data[id]);
} }
@ -166,9 +166,16 @@ app.post('/admin/privilege', async (req, res) => {
app.get('/admin/rating', async (req, res) => { app.get('/admin/rating', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
const contests = await Contest.query(null, {}, [['start_time', 'desc']]); const contests = await Contest.find({
const calcs = await RatingCalculation.query(null, {}, [['id', 'desc']]); order: {
const util = require('util'); start_time: 'DESC'
}
});
const calcs = await RatingCalculation.find({
order: {
id: 'DESC'
}
});
for (const calc of calcs) await calc.loadRelationships(); for (const calc of calcs) await calc.loadRelationships();
res.render('admin_rating', { res.render('admin_rating', {
@ -186,7 +193,7 @@ app.get('/admin/rating', async (req, res) => {
app.post('/admin/rating/add', async (req, res) => { app.post('/admin/rating/add', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); 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('无此比赛'); if (!contest) throw new ErrorMessage('无此比赛');
await contest.loadRelationships(); await contest.loadRelationships();
@ -199,7 +206,7 @@ app.post('/admin/rating/add', async (req, res) => {
const players = []; const players = [];
for (let i = 1; i <= contest.ranklist.ranklist.player_num; i++) { 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({ players.push({
user: user, user: user,
rank: i, rank: i,
@ -227,7 +234,14 @@ app.post('/admin/rating/add', async (req, res) => {
app.post('/admin/rating/delete', async (req, res) => { app.post('/admin/rating/delete', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); 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 不正确'); if (calcList.length === 0) throw new ErrorMessage('ID 不正确');
for (let i = 0; i < calcList.length; i++) { 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 (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
if (req.body.type === 'reset_count') { if (req.body.type === 'reset_count') {
const problems = await Problem.query(); const problems = await Problem.find();
for (const p of problems) { for (const p of problems) {
await p.resetSubmissionCount(); await p.resetSubmissionCount();
} }
} else if (req.body.type === 'reset_discussion') { } else if (req.body.type === 'reset_discussion') {
const articles = await Article.query(); const articles = await Article.find();
for (const a of articles) { for (const a of articles) {
await a.resetReplyCountAndTime(); await a.resetReplyCountAndTime();
} }
} else if (req.body.type === 'reset_codelen') { } else if (req.body.type === 'reset_codelen') {
const submissions = await JudgeState.query(); const submissions = await JudgeState.find();
for (const s of submissions) { for (const s of submissions) {
if (s.type !== 'submit-answer') { if (s.type !== 'submit-answer') {
s.code_length = s.code.length; s.code_length = s.code.length;
@ -310,60 +324,55 @@ app.post('/admin/rejudge', async (req, res) => {
try { try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。'); 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 user = await User.fromName(req.body.submitter || '');
let where = {}; if (user) {
if (user) where.user_id = user.id; query.andWhere('user_id = :user_id', { user_id: user.id });
else if (req.body.submitter) where.user_id = -1; } else if (req.body.submitter) {
query.andWhere('user_id = :user_id', { user_id: 0 });
}
let minID = parseInt(req.body.min_id); 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); let maxID = parseInt(req.body.max_id);
if (isNaN(maxID)) maxID = 2147483647; if (!isNaN(maxID)) query.andWhere('id <= :maxID', { maxID })
where.id = {
$and: {
$gte: parseInt(minID),
$lte: parseInt(maxID)
}
};
let minScore = parseInt(req.body.min_score); 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); let maxScore = parseInt(req.body.max_score);
if (isNaN(maxScore)) maxScore = 100; if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore });
if (!(minScore === 0 && maxScore === 100)) {
where.score = {
$and: {
$gte: parseInt(minScore),
$lte: parseInt(maxScore)
}
};
}
let minTime = syzoj.utils.parseDate(req.body.min_time); 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); 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 = { if (req.body.language) {
$and: { if (req.body.language === 'submit-answer') {
$gte: parseInt(minTime), query.andWhere(new TypeORM.Brackets(qb => {
$lte: parseInt(maxTime) 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.status) {
if (req.body.language === 'submit-answer') where.language = { $or: [{ $eq: '', }, { $eq: null }] }; query.andWhere('status LIKE :status', { status: req.body.status + '%' });
else if (req.body.language === 'non-submit-answer') where.language = { $not: '' }; }
else where.language = req.body.language;
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') { if (req.body.type === 'rejudge') {
let submissions = await JudgeState.query(null, where); let submissions = await JudgeState.queryAll(query);
for (let submission of submissions) { for (let submission of submissions) {
await submission.rejudge(); await submission.rejudge();
} }

14
modules/api.js

@ -111,7 +111,11 @@ app.post('/api/sign_up', async (req, res) => {
username: req.body.username, username: req.body.username,
password: req.body.password, password: req.body.password,
email: req.body.email, email: req.body.email,
public_email: true public_email: true,
is_show: syzoj.config.default.user.show,
rating: syzoj.config.default.user.rating,
register_time: parseInt((new Date()).getTime() / 1000),
prefer_formatted_code: true
}); });
await user.save(); await user.save();
@ -156,7 +160,7 @@ app.post('/api/reset_password', async (req, res) => {
let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2'; let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2';
if (req.body.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。'); 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; user.password = req.body.password;
await user.save(); await user.save();
@ -196,7 +200,11 @@ app.get('/api/sign_up_confirm', async (req, res) => {
username: obj.username, username: obj.username,
password: obj.password, password: obj.password,
email: obj.email, email: obj.email,
public_email: true public_email: true,
is_show: syzoj.config.default.user.show,
rating: syzoj.config.default.user.rating,
register_time: parseInt((new Date()).getTime() / 1000),
prefer_formatted_code: true
}); });
await user.save(); await user.save();

38
modules/api_v2.js

@ -4,19 +4,23 @@ app.get('/api/v2/search/users/:keyword*?', async (req, res) => {
let keyword = req.params.keyword || ''; let keyword = req.params.keyword || '';
let conditions = []; let conditions = [];
const uid = parseInt(keyword); const uid = parseInt(keyword) || 0;
if (uid != null && !isNaN(uid)) { if (uid != null && !isNaN(uid)) {
conditions.push({ id: uid }); conditions.push({ id: uid });
} }
if (keyword != null && String(keyword).length >= 2) { 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) { if (conditions.length === 0) {
res.send({ success: true, results: [] }); res.send({ success: true, results: [] });
} else { } else {
let users = await User.query(null, { let users = await User.find({
$or: conditions where: conditions,
}, [['username', 'asc']]); order: {
username: 'ASC'
}
});
let result = []; let result = [];
@ -34,15 +38,20 @@ app.get('/api/v2/search/problems/:keyword*?', async (req, res) => {
let Problem = syzoj.model('problem'); let Problem = syzoj.model('problem');
let keyword = req.params.keyword || ''; let keyword = req.params.keyword || '';
let problems = await Problem.query(null, { let problems = await Problem.find({
title: { $like: `%${req.params.keyword}%` } where: {
}, [['id', 'asc']]); title: TypeORM.Like(`%${req.params.keyword}%`)
},
order: {
id: 'ASC'
}
});
let result = []; let result = [];
let id = parseInt(keyword); let id = parseInt(keyword);
if (id) { if (id) {
let problemById = await Problem.fromID(parseInt(keyword)); let problemById = await Problem.findById(parseInt(keyword));
if (problemById && await problemById.isAllowedUseBy(res.locals.user)) { if (problemById && await problemById.isAllowedUseBy(res.locals.user)) {
result.push(problemById); result.push(problemById);
} }
@ -67,9 +76,14 @@ app.get('/api/v2/search/tags/:keyword*?', async (req, res) => {
let ProblemTag = syzoj.model('problem_tag'); let ProblemTag = syzoj.model('problem_tag');
let keyword = req.params.keyword || ''; let keyword = req.params.keyword || '';
let tags = await ProblemTag.query(null, { let tags = await ProblemTag.find({
name: { $like: `%${req.params.keyword}%` } where: {
}, [['name', 'asc']]); name: TypeORM.Like(`%${req.params.keyword}%`)
},
order: {
name: 'ASC'
}
});
let result = tags.slice(0, syzoj.config.page.edit_problem_tag_list); let result = tags.slice(0, syzoj.config.page.edit_problem_tag_list);

116
modules/contest.js

@ -15,7 +15,9 @@ app.get('/contests', async (req, res) => {
else where = { is_public: true }; else where = { is_public: true };
let paginate = syzoj.utils.paginate(await Contest.count(where), req.query.page, syzoj.config.page.contest); 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)); 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('您没有权限进行此操作。'); if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
let contest_id = parseInt(req.params.id); let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id); let contest = await Contest.findById(contest_id);
if (!contest) { if (!contest) {
contest = await Contest.create(); contest = await Contest.create();
contest.id = 0; contest.id = 0;
@ -45,8 +47,8 @@ app.get('/contest/:id/edit', async (req, res) => {
} }
let problems = [], admins = []; let problems = [], admins = [];
if (contest.problems) problems = await contest.problems.split('|').mapAsync(async id => await Problem.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.fromID(id)); if (contest.admins) admins = await contest.admins.split('|').mapAsync(async id => await User.findById(id));
res.render('contest_edit', { res.render('contest_edit', {
contest: contest, 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('您没有权限进行此操作。'); if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
let contest_id = parseInt(req.params.id); let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id); let contest = await Contest.findById(contest_id);
let ranklist = null; let ranklist = null;
if (!contest) { if (!contest) {
contest = await Contest.create(); contest = await Contest.create();
@ -120,7 +122,7 @@ app.get('/contest/:id', async (req, res) => {
const curUser = res.locals.user; const curUser = res.locals.user;
let contest_id = parseInt(req.params.id); 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) throw new ErrorMessage('无此比赛。');
if (!contest.is_public && (!res.locals.user || !res.locals.user.is_admin)) 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); contest.information = await syzoj.utils.markdown(contest.information);
let problems_id = await contest.getProblems(); 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; let player = null;
@ -147,7 +149,7 @@ app.get('/contest/:id', async (req, res) => {
for (let problem of problems) { for (let problem of problems) {
if (contest.type === 'noi') { if (contest.type === 'noi') {
if (player.score_details[problem.problem.id]) { 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.status = judge_state.status;
if (!contest.ended && !await problem.problem.isAllowedEditBy(res.locals.user) && !['Compile Error', 'Waiting', 'Compiling'].includes(problem.status)) { if (!contest.ended && !await problem.problem.isAllowedEditBy(res.locals.user) && !['Compile Error', 'Waiting', 'Compiling'].includes(problem.status)) {
problem.status = 'Submitted'; problem.status = 'Submitted';
@ -156,7 +158,7 @@ app.get('/contest/:id', async (req, res) => {
} }
} else if (contest.type === 'ioi') { } else if (contest.type === 'ioi') {
if (player.score_details[problem.problem.id]) { 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.status = judge_state.status;
problem.judge_id = player.score_details[problem.problem.id].judge_id; problem.judge_id = player.score_details[problem.problem.id].judge_id;
await contest.loadRelationships(); await contest.loadRelationships();
@ -222,7 +224,7 @@ app.get('/contest/:id', async (req, res) => {
app.get('/contest/:id/ranklist', async (req, res) => { app.get('/contest/:id/ranklist', async (req, res) => {
try { try {
let contest_id = parseInt(req.params.id); 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; const curUser = res.locals.user;
if (!contest) throw new ErrorMessage('无此比赛。'); 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]); 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 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') { if (contest.type === 'noi' || contest.type === 'ioi') {
player.score = 0; player.score = 0;
} }
for (let i in player.score_details) { 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() ***/ /*** XXX: Clumsy duplication, see ContestRanklist::updatePlayer() ***/
if (contest.type === 'noi' || contest.type === 'ioi') { 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 { return {
user: user, user: user,
@ -264,7 +266,7 @@ app.get('/contest/:id/ranklist', async (req, res) => {
}); });
let problems_id = await contest.getProblems(); 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', { res.render('contest_ranklist', {
contest: contest, contest: contest,
@ -296,7 +298,7 @@ function getDisplayConfig(contest) {
app.get('/contest/:id/submissions', async (req, res) => { app.get('/contest/:id/submissions', async (req, res) => {
try { try {
let contest_id = parseInt(req.params.id); 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.is_public && (!res.locals.user || !res.locals.user.is_admin)) throw new ErrorMessage('比赛未公开,请耐心等待 (´∀ `)');
if (contest.isEnded()) { if (contest.isEnded()) {
@ -309,56 +311,68 @@ app.get('/contest/:id/submissions', async (req, res) => {
const curUser = res.locals.user; const curUser = res.locals.user;
let user = req.query.submitter && await User.fromName(req.query.submitter); 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 (displayConfig.showOthers) {
if (user) { if (user) {
where.user_id = user.id; query.andWhere('user_id = :user_id', { user_id: user.id });
isFiltered = true;
} }
} else { } else {
if (curUser == null || // Not logined if (curUser == null || // Not logined
(user && user.id !== curUser.id)) { // Not querying himself (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) { if (displayConfig.showScore) {
let minScore = parseInt(req.query.min_score); let minScore = parseInt(req.body.min_score);
let maxScore = parseInt(req.query.max_score); if (!isNaN(minScore)) query.andWhere('score >= :minScore', { minScore });
let maxScore = parseInt(req.body.max_score);
if (!isNaN(minScore) || !isNaN(maxScore)) { if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore });
if (isNaN(minScore)) minScore = 0;
if (isNaN(maxScore)) maxScore = 100; if (!isNaN(minScore) || !isNaN(maxScore)) isFiltered = true;
if (!(minScore === 0 && maxScore === 100)) {
where.score = {
$and: {
$gte: parseInt(minScore),
$lte: parseInt(maxScore)
}
};
}
}
} }
if (req.query.language) { if (req.query.language) {
if (req.query.language === 'submit-answer') where.language = ''; if (req.body.language === 'submit-answer') {
else where.language = req.query.language; 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 (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]; if (req.query.problem_id) {
where.type = 1; problem_id = problems_id[parseInt(req.query.problem_id) - 1] || 0;
where.type_info = contest_id; 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 paginate = syzoj.utils.paginate(await JudgeState.countQuery(query), req.query.page, syzoj.config.page.judge_state);
let judge_state = await JudgeState.query(paginate, where, [['submit_time', 'desc']]); let judge_state = await JudgeState.queryPage(paginate, query, {
submit_time: 'DESC'
});
await judge_state.forEachAsync(async obj => { await judge_state.forEachAsync(async obj => {
await obj.loadRelationships(); await obj.loadRelationships();
@ -397,7 +411,7 @@ app.get('/contest/:id/submissions', async (req, res) => {
app.get('/contest/submission/:id', async (req, res) => { app.get('/contest/submission/:id', async (req, res) => {
try { try {
const id = parseInt(req.params.id); const id = parseInt(req.params.id);
const judge = await JudgeState.fromID(id); const judge = await JudgeState.findById(id);
if (!judge) throw new ErrorMessage("提交记录 ID 不正确。"); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。");
const curUser = res.locals.user; const curUser = res.locals.user;
if ((!curUser) || judge.user_id !== curUser.id) throw new ErrorMessage("您没有权限执行此操作。"); 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])); 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(); contest.ended = contest.isEnded();
const displayConfig = getDisplayConfig(contest); 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) => { app.get('/contest/:id/problem/:pid', async (req, res) => {
try { try {
let contest_id = parseInt(req.params.id); 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) throw new ErrorMessage('无此比赛。');
const curUser = res.locals.user; 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('无此题目。'); if (!pid || pid < 1 || pid > problems_id.length) throw new ErrorMessage('无此题目。');
let problem_id = problems_id[pid - 1]; let problem_id = problems_id[pid - 1];
let problem = await Problem.fromID(problem_id); let problem = await Problem.findById(problem_id);
await problem.loadRelationships(); await problem.loadRelationships();
contest.ended = contest.isEnded(); 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) => { app.get('/contest/:id/:pid/download/additional_file', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let contest = await Contest.fromID(id); let contest = await Contest.findById(id);
if (!contest) throw new ErrorMessage('无此比赛。'); if (!contest) throw new ErrorMessage('无此比赛。');
let problems_id = await contest.getProblems(); 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('无此题目。'); if (!pid || pid < 1 || pid > problems_id.length) throw new ErrorMessage('无此题目。');
let problem_id = problems_id[pid - 1]; 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(); contest.ended = contest.isEnded();
if (!(contest.isRunning() || contest.isEnded())) { if (!(contest.isRunning() || contest.isEnded())) {

36
modules/discussion.js

@ -12,17 +12,19 @@ app.get('/discussion/:type?', async (req, res) => {
let where; let where;
if (in_problems) { if (in_problems) {
where = { problem_id: { $not: null } }; where = { problem_id: TypeORM.Not(null) };
} else { } 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 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) { for (let article of articles) {
await article.loadRelationships(); await article.loadRelationships();
if (in_problems) { 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) => { app.get('/discussion/problem/:pid', async (req, res) => {
try { try {
let pid = parseInt(req.params.pid); let pid = parseInt(req.params.pid);
let problem = await Problem.fromID(pid); let problem = await Problem.findById(pid);
if (!problem) throw new ErrorMessage('无此题目。'); if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) { if (!await problem.isAllowedUseBy(res.locals.user)) {
throw new ErrorMessage('您没有权限进行此操作。'); throw new ErrorMessage('您没有权限进行此操作。');
@ -51,7 +53,9 @@ app.get('/discussion/problem/:pid', async (req, res) => {
let where = { problem_id: pid }; let where = { problem_id: pid };
let paginate = syzoj.utils.paginate(await Article.count(where), req.query.page, syzoj.config.page.discussion); 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(); 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) => { app.get('/article/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let article = await Article.fromID(id); let article = await Article.findById(id);
if (!article) throw new ErrorMessage('无此帖子。'); if (!article) throw new ErrorMessage('无此帖子。');
await article.loadRelationships(); await article.loadRelationships();
@ -84,7 +88,9 @@ app.get('/article/:id', async (req, res) => {
let commentsCount = await ArticleComment.count(where); let commentsCount = await ArticleComment.count(where);
let paginate = syzoj.utils.paginate(commentsCount, req.query.page, syzoj.config.page.article_comment); 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) { for (let comment of comments) {
comment.content = await syzoj.utils.markdown(comment.content); comment.content = await syzoj.utils.markdown(comment.content);
@ -94,7 +100,7 @@ app.get('/article/:id', async (req, res) => {
let problem = null; let problem = null;
if (article.problem_id) { 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)) { if (!await problem.isAllowedUseBy(res.locals.user)) {
throw new ErrorMessage('您没有权限进行此操作。'); 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 }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let article = await Article.fromID(id); let article = await Article.findById(id);
if (!article) { if (!article) {
article = await Article.create(); 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 }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let article = await Article.fromID(id); let article = await Article.findById(id);
let time = syzoj.utils.getCurrentDate(); let time = syzoj.utils.getCurrentDate();
if (!article) { if (!article) {
@ -155,7 +161,7 @@ app.post('/article/:id/edit', async (req, res) => {
article.public_time = article.sort_time = time; article.public_time = article.sort_time = time;
if (req.query.problem_id) { 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('无此题目。'); if (!problem) throw new ErrorMessage('无此题目。');
article.problem_id = problem.id; article.problem_id = problem.id;
} else { } 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 }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let article = await Article.fromID(id); let article = await Article.findById(id);
if (!article) { if (!article) {
throw new ErrorMessage('无此帖子。'); 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 }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let article = await Article.fromID(id); let article = await Article.findById(id);
if (!article) { if (!article) {
throw new ErrorMessage('无此帖子。'); 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 }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let comment = await ArticleComment.fromID(id); let comment = await ArticleComment.findById(id);
if (!comment) { if (!comment) {
throw new ErrorMessage('无此评论。'); throw new ErrorMessage('无此评论。');

17
modules/index.js

@ -10,10 +10,15 @@ const timeAgo = new TimeAgo('zh-CN');
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
try { 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()); 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, title: article.title,
url: syzoj.utils.makeUrl(['article', article.id]), url: syzoj.utils.makeUrl(['article', article.id]),
date: syzoj.utils.formatDate(article.public_time, 'L') 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); 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, id: problem.id,
title: problem.title, title: problem.title,
time: timeAgo.format(new Date(problem.publicize_time)), time: timeAgo.format(new Date(problem.publicize_time)),

203
modules/problem.js

@ -1,13 +1,12 @@
let Problem = syzoj.model('problem'); let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state'); let JudgeState = syzoj.model('judge_state');
let FormattedCode = syzoj.model('formatted_code'); let FormattedCode = syzoj.model('formatted_code');
let CustomTest = syzoj.model('custom_test');
let WaitingJudge = syzoj.model('waiting_judge');
let Contest = syzoj.model('contest'); let Contest = syzoj.model('contest');
let ProblemTag = syzoj.model('problem_tag'); let ProblemTag = syzoj.model('problem_tag');
let ProblemTagMap = syzoj.model('problem_tag_map');
let Article = syzoj.model('article'); let Article = syzoj.model('article');
const Sequelize = require('sequelize');
const randomstring = require('randomstring');
const fs = require('fs-extra');
let Judger = syzoj.lib('judger'); let Judger = syzoj.lib('judger');
let CodeFormatter = syzoj.lib('code_formatter'); let CodeFormatter = syzoj.lib('code_formatter');
@ -20,28 +19,24 @@ app.get('/problems', async (req, res) => {
throw new ErrorMessage('错误的排序参数。'); throw new ErrorMessage('错误的排序参数。');
} }
let sortVal = sort; let query = Problem.createQueryBuilder();
if (sort === 'ac_rate') {
sortVal = { raw: 'ac_num / submit_num' };
}
let where = {};
if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) { if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) {
if (res.locals.user) { if (res.locals.user) {
where = { query.where('is_public = 1')
$or: { .orWhere('user_id = :user_id', { user_id: res.locals.user.id });
is_public: 1,
user_id: res.locals.user.id
}
};
} else { } else {
where = { query.where('is_public = 1');
is_public: 1
};
} }
} }
let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem); if (sort === 'ac_rate') {
let problems = await Problem.query(paginate, where, [[sortVal, order]]); 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 => { await problems.forEachAsync(async problem => {
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user); problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
@ -73,51 +68,51 @@ app.get('/problems/search', async (req, res) => {
throw new ErrorMessage('错误的排序参数。'); throw new ErrorMessage('错误的排序参数。');
} }
let where = { let query = Problem.createQueryBuilder();
$or: {
title: { $like: `%${req.query.keyword}%` },
id: id
}
};
if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) { if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) {
if (res.locals.user) { if (res.locals.user) {
where = { query.where(new TypeORM.Brackets(qb => {
$and: [ qb.where('is_public = 1')
where, .orWhere('user_id = :user_id', { user_id: res.locals.user.id })
{ }))
$or: { .andWhere(new TypeORM.Brackets(qb => {
is_public: 1, qb.where('title LIKE :title', { title: `%${req.query.keyword}%` })
user_id: res.locals.user.id .orWhere('id = :id', { id: id })
} }));
}
]
};
} else { } else {
where = { query.where('is_public = 1')
$and: [ .andWhere(new TypeORM.Brackets(qb => {
where, qb.where('title LIKE :title', { title: `%${req.query.keyword}%` })
{ .orWhere('id = :id', { id: id })
is_public: 1 }));
}
]
};
} }
} else {
query.where('title LIKE :title', { title: `%${req.query.keyword}%` })
.orWhere('id = :id', { id: id })
} }
let sortVal = sort;
if (sort === 'ac_rate') { 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 paginate = syzoj.utils.paginate(await Problem.countQuery(query), req.query.page, syzoj.config.page.problem);
let problems = await Problem.query(paginate, where, [syzoj.db.literal('`id` = ' + id + ' DESC'), [sortVal, order]]); 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.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.judge_state = await problem.getJudgeState(res.locals.user, true); problem.judge_state = await problem.getJudgeState(res.locals.user, true);
problem.tags = await problem.getTags(); 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', { res.render('problems', {
allowedManageTag: res.locals.user && await res.locals.user.hasPrivilege('manage_problem_tag'), 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) => { app.get('/problems/tag/:tagIDs', async (req, res) => {
try { try {
let tagIDs = Array.from(new Set(req.params.tagIDs.split(',').map(x => parseInt(x)))); 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 sort = req.query.sort || syzoj.config.sorting.problem.field;
const order = req.query.order || syzoj.config.sorting.problem.order; 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)) { 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) { for (let tagID of tagIDs) {
if (tagID !== tagIDs[0]) { if (tagID !== tagIDs[0]) {
sql += 'AND\n'; 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()); 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.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.judge_state = await problem.getJudgeState(res.locals.user, true); problem.judge_state = await problem.getJudgeState(res.locals.user, true);
problem.tags = await problem.getTags(); problem.tags = await problem.getTags();
return problem;
}); });
res.render('problems', { res.render('problems', {
@ -202,7 +202,7 @@ app.get('/problems/tag/:tagIDs', async (req, res) => {
app.get('/problem/:id', async (req, res) => { app.get('/problem/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) { 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) => { app.get('/problem/:id/export', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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('无此题目。'); if (!problem || !problem.is_public) throw new ErrorMessage('无此题目。');
let obj = { let obj = {
@ -279,11 +279,15 @@ app.get('/problem/:id/export', async (req, res) => {
app.get('/problem/:id/edit', async (req, res) => { app.get('/problem/:id/edit', async (req, res) => {
try { try {
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id); let problem = await Problem.findById(id);
if (!problem) { if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); 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.id = id;
problem.allowedEdit = true; problem.allowedEdit = true;
problem.tags = []; problem.tags = [];
@ -310,16 +314,20 @@ app.get('/problem/:id/edit', async (req, res) => {
app.post('/problem/:id/edit', async (req, res) => { app.post('/problem/:id/edit', async (req, res) => {
try { try {
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id); let problem = await Problem.findById(id);
if (!problem) { if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); 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')) { if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id); let customID = parseInt(req.body.id);
if (customID) { if (customID) {
if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。'); if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。');
problem.id = customID; problem.id = customID;
} else if (id) problem.id = id; } 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')) { if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id); let customID = parseInt(req.body.id);
if (customID && customID !== 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); await problem.changeID(customID);
} }
} }
@ -357,7 +365,7 @@ app.post('/problem/:id/edit', async (req, res) => {
req.body.tags = [req.body.tags]; 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); await problem.setTags(newTagIDs);
res.redirect(syzoj.utils.makeUrl(['problem', problem.id])); 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) => { app.get('/problem/:id/import', async (req, res) => {
try { try {
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id); let problem = await Problem.findById(id);
if (!problem) { if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); 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.id = id;
problem.new = true; problem.new = true;
problem.user_id = res.locals.user.id; 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) => { app.post('/problem/:id/import', async (req, res) => {
try { try {
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id); let problem = await Problem.findById(id);
if (!problem) { if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) }); 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')) { if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id); let customID = parseInt(req.body.id);
if (customID) { if (customID) {
if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。'); if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。');
problem.id = customID; problem.id = customID;
} else if (id) problem.id = id; } else if (id) problem.id = id;
} }
@ -460,15 +476,14 @@ app.post('/problem/:id/import', async (req, res) => {
let download = require('download'); let download = require('download');
let tmp = require('tmp-promise'); let tmp = require('tmp-promise');
let tmpFile = await tmp.file(); let tmpFile = await tmp.file();
let fs = require('bluebird').promisifyAll(require('fs'));
try { try {
let data = await download(req.body.url + (req.body.url.endsWith('/') ? 'testdata/download' : '/testdata/download')); 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')); await problem.updateTestdata(tmpFile.path, await res.locals.user.hasPrivilege('manage_problem'));
if (json.obj.have_additional_file) { if (json.obj.have_additional_file) {
let additional_file = await download(req.body.url + (req.body.url.endsWith('/') ? 'download/additional_file' : '/download/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')); await problem.updateFile(tmpFile.path, 'additional_file', await res.locals.user.hasPrivilege('manage_problem'));
} }
} catch (e) { } catch (e) {
@ -488,7 +503,7 @@ app.post('/problem/:id/import', async (req, res) => {
app.get('/problem/:id/manage', async (req, res) => { app.get('/problem/:id/manage', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) 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) => { app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) 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) { async function setPublic(req, res, is_public) {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
let allowedManage = await problem.isAllowedManageBy(res.locals.user); let allowedManage = await problem.isAllowedManageBy(res.locals.user);
@ -571,10 +586,7 @@ async function setPublic(req, res, is_public) {
problem.publicize_time = new Date(); problem.publicize_time = new Date();
await problem.save(); await problem.save();
JudgeState.model.update( JudgeState.query('UPDATE `judge_state` SET `is_public` = ' + is_public + ' WHERE `problem_id` = ' + id);
{ is_public: is_public },
{ where: { problem_id: id } }
);
res.redirect(syzoj.utils.makeUrl(['problem', id])); res.redirect(syzoj.utils.makeUrl(['problem', id]));
} catch (e) { } 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) => { app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let problem = await Problem.fromID(id); let problem = await Problem.findById(id);
const curUser = res.locals.user; const curUser = res.locals.user;
if (!problem) throw new ErrorMessage('无此题目。'); 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('上传答案文件失败。'); if (!file.md5) throw new ErrorMessage('上传答案文件失败。');
judge_state = await JudgeState.create({ judge_state = await JudgeState.create({
submit_time: parseInt((new Date()).getTime() / 1000),
status: 'Unknown',
task_id: randomstring.generate(10),
code: file.md5, code: file.md5,
code_length: size, code_length: size,
language: null, language: null,
@ -636,14 +651,16 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
let code; let code;
if (req.files['answer']) { if (req.files['answer']) {
if (req.files['answer'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码文件太大。'); if (req.files['answer'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码文件太大。');
let fs = Promise.promisifyAll(require('fs')); code = (await fs.readFile(req.files['answer'][0].path)).toString();
code = (await fs.readFileAsync(req.files['answer'][0].path)).toString();
} else { } else {
if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码太长。'); if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码太长。');
code = req.body.code; code = req.body.code;
} }
judge_state = await JudgeState.create({ judge_state = await JudgeState.create({
submit_time: parseInt((new Date()).getTime() / 1000),
status: 'Unknown',
task_id: randomstring.generate(10),
code: code, code: code,
code_length: code.length, code_length: code.length,
language: req.body.language, 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_id = parseInt(req.query.contest_id);
let contest; let contest;
if (contest_id) { if (contest_id) {
contest = await Contest.fromID(contest_id); contest = await Contest.findById(contest_id);
if (!contest) throw new ErrorMessage('无此比赛。'); if (!contest) throw new ErrorMessage('无此比赛。');
if ((!contest.isRunning()) && (!await contest.isSupervisior(curUser))) throw new ErrorMessage('比赛未开始或已结束。'); if ((!contest.isRunning()) && (!await contest.isSupervisior(curUser))) throw new ErrorMessage('比赛未开始或已结束。');
let problems_id = await contest.getProblems(); 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) => { app.post('/problem/:id/delete', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!problem.isAllowedManageBy(res.locals.user)) 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) => { app.get('/problem/:id/testdata', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) 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) => { app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) 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) => { app.post('/problem/:id/testdata/delete/:filename', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) 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) => { app.get('/problem/:id/testdata/download/:filename?', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) 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) => { app.get('/problem/:id/download/additional_file', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
// XXX: Reduce duplication (see the '/problem/:id/submit' handler) // XXX: Reduce duplication (see the '/problem/:id/submit' handler)
let contest_id = parseInt(req.query.contest_id); let contest_id = parseInt(req.query.contest_id);
if (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) throw new ErrorMessage('无此比赛。');
if (!contest.isRunning()) throw new ErrorMessage('比赛未开始或已结束。'); if (!contest.isRunning()) throw new ErrorMessage('比赛未开始或已结束。');
let problems_id = await contest.getProblems(); 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) => { app.get('/problem/:id/statistics/:type', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) 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) => { app.post('/problem/:id/custom-test', app.multer.fields([{ name: 'code_upload', maxCount: 1 }, { name: 'input_file', maxCount: 1 }]), async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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) throw new ErrorMessage('无此题目。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });

4
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('您没有权限进行此操作。'); if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem_tag')) throw new ErrorMessage('您没有权限进行此操作。');
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let tag = await ProblemTag.fromID(id); let tag = await ProblemTag.findById(id);
if (!tag) { if (!tag) {
tag = await ProblemTag.create(); 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('您没有权限进行此操作。'); if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem_tag')) throw new ErrorMessage('您没有权限进行此操作。');
let id = parseInt(req.params.id) || 0; let id = parseInt(req.params.id) || 0;
let tag = await ProblemTag.fromID(id); let tag = await ProblemTag.findById(id);
if (!tag) { if (!tag) {
tag = await ProblemTag.create(); tag = await ProblemTag.create();

90
modules/submission.js

@ -23,23 +23,32 @@ const displayConfig = {
app.get('/submissions', async (req, res) => { app.get('/submissions', async (req, res) => {
try { try {
const curUser = res.locals.user; 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; 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) { if (!req.query.contest) {
where.type = { $eq: 0 }; query.andWhere('type = 0');
} else { } else {
const contestId = Number(req.query.contest); const contestId = Number(req.query.contest);
const contest = await Contest.fromID(contestId); const contest = await Contest.findById(contestId);
contest.ended = contest.isEnded(); contest.ended = contest.isEnded();
if ((contest.ended && contest.is_public) || // If the contest is ended and is not hidden 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 (curUser && await contest.isSupervisior(curUser)) // Or if the user have the permission to check
) { ) {
where.type = { $eq: 1 }; query.andWhere('type = 1');
where.type_info = { $eq: contestId }; query.andWhere('type_info = :type_info', { type_info: contestId });
inContest = true; inContest = true;
} else { } else {
throw new Error("您暂时无权查看此比赛的详细评测信息。"); throw new Error("您暂时无权查看此比赛的详细评测信息。");
@ -47,56 +56,55 @@ app.get('/submissions', async (req, res) => {
} }
let minScore = parseInt(req.query.min_score); let minScore = parseInt(req.query.min_score);
if (!isNaN(minScore)) query.andWhere('score >= :minScore', { minScore });
let maxScore = parseInt(req.query.max_score); let maxScore = parseInt(req.query.max_score);
if (!isNaN(maxScore)) query.andWhere('score <= :maxScore', { maxScore });
if (!isNaN(minScore) || !isNaN(maxScore)) { if (!isNaN(minScore) || !isNaN(maxScore)) isFiltered = true;
if (isNaN(minScore)) minScore = 0;
if (isNaN(maxScore)) maxScore = 100; if (req.query.language) {
if (!(minScore === 0 && maxScore === 100)) { if (req.query.language === 'submit-answer') {
where.score = { query.andWhere(new TypeORM.Brackets(qb => {
$and: { qb.orWhere('language = :language', { language: '' })
$gte: parseInt(minScore), .orWhere('language IS NULL');
$lte: parseInt(maxScore) }));
} 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.status) {
if (req.query.language === 'submit-answer') where.language = { $or: [{ $eq: '', }, { $eq: null }] }; query.andWhere('status LIKE :status', { status: req.query.status + '%' });
else if (req.query.language === 'non-submit-answer') where.language = { $not: '' }; isFiltered = true;
else where.language = req.query.language;
} }
if (req.query.status) where.status = { $like: req.query.status + '%' };
if (!inContest && (!curUser || !await curUser.hasPrivilege('manage_problem'))) { if (!inContest && (!curUser || !await curUser.hasPrivilege('manage_problem'))) {
if (req.query.problem_id) { if (req.query.problem_id) {
let problem_id = parseInt(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) if (!problem)
throw new ErrorMessage("无此题目。"); throw new ErrorMessage("无此题目。");
if (await problem.isAllowedUseBy(res.locals.user)) { if (await problem.isAllowedUseBy(res.locals.user)) {
where.problem_id = { query.andWhere('problem_id = :problem_id', { problem_id: parseInt(req.query.problem_id) || 0 });
$and: [ isFiltered = true;
{ $eq: where.problem_id = problem_id }
]
};
} else { } else {
throw new ErrorMessage("您没有权限进行此操作。"); throw new ErrorMessage("您没有权限进行此操作。");
} }
} else { } else {
where.is_public = { query.andWhere('is_public = true');
$eq: true,
};
} }
} else { } else if (req.query.problem_id) {
if (req.query.problem_id) where.problem_id = parseInt(req.query.problem_id) || -1; 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.countQuery(query), req.query.page, syzoj.config.page.judge_state);
let judge_state = await JudgeState.queryPage(paginate, query, { id: "DESC" });
let paginate = syzoj.utils.paginate(await JudgeState.count(where), req.query.page, syzoj.config.page.judge_state);
let judge_state = await JudgeState.query(paginate, where, [['id', 'desc']], true);
await judge_state.forEachAsync(async obj => { await judge_state.forEachAsync(async obj => {
await obj.loadRelationships(); await obj.loadRelationships();
@ -132,14 +140,14 @@ app.get('/submissions', async (req, res) => {
app.get('/submission/:id', async (req, res) => { app.get('/submission/:id', async (req, res) => {
try { try {
const id = parseInt(req.params.id); const id = parseInt(req.params.id);
const judge = await JudgeState.fromID(id); const judge = await JudgeState.findById(id);
if (!judge) throw new ErrorMessage("提交记录 ID 不正确。"); if (!judge) throw new ErrorMessage("提交记录 ID 不正确。");
const curUser = res.locals.user; const curUser = res.locals.user;
if (!await judge.isAllowedVisitBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。'); if (!await judge.isAllowedVisitBy(curUser)) throw new ErrorMessage('您没有权限进行此操作。');
let contest; let contest;
if (judge.type === 1) { if (judge.type === 1) {
contest = await Contest.fromID(judge.type_info); contest = await Contest.findById(judge.type_info);
contest.ended = contest.isEnded(); contest.ended = contest.isEnded();
if ((!contest.ended || !contest.is_public) && 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) => { app.post('/submission/:id/rejudge', async (req, res) => {
try { try {
let id = parseInt(req.params.id); 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('无法重新评测一个评测中的提交。'); if (judge.pending && !(res.locals.user && await res.locals.user.hasPrivilege('manage_problem'))) throw new ErrorMessage('无法重新评测一个评测中的提交。');

15
modules/user.js

@ -13,7 +13,7 @@ app.get('/ranklist', async (req, res) => {
throw new ErrorMessage('错误的排序参数。'); throw new ErrorMessage('错误的排序参数。');
} }
let paginate = syzoj.utils.paginate(await User.count({ is_show: true }), req.query.page, syzoj.config.page.ranklist); 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()); await ranklist.forEachAsync(async x => x.renderInformation());
res.render('ranklist', { res.render('ranklist', {
@ -76,7 +76,7 @@ app.post('/logout', async (req, res) => {
app.get('/user/:id', async (req, res) => { app.get('/user/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let user = await User.fromID(id); let user = await User.findById(id);
if (!user) throw new ErrorMessage('无此用户。'); if (!user) throw new ErrorMessage('无此用户。');
user.ac_problems = await user.getACProblems(); user.ac_problems = await user.getACProblems();
user.articles = await user.getArticles(); user.articles = await user.getArticles();
@ -86,7 +86,10 @@ app.get('/user/:id', async (req, res) => {
await user.renderInformation(); await user.renderInformation();
user.emailVisible = user.public_email || user.allowedEdit; 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 = [{ const ratingHistories = [{
contestName: "初始积分", contestName: "初始积分",
value: syzoj.config.default.user.rating, value: syzoj.config.default.user.rating,
@ -95,7 +98,7 @@ app.get('/user/:id', async (req, res) => {
}]; }];
for (const history of ratingHistoryValues) { 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({ ratingHistories.push({
contestName: contest.title, contestName: contest.title,
value: history.rating_after, value: history.rating_after,
@ -122,7 +125,7 @@ app.get('/user/:id', async (req, res) => {
app.get('/user/:id/edit', async (req, res) => { app.get('/user/:id/edit', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let user = await User.fromID(id); let user = await User.findById(id);
if (!user) throw new ErrorMessage('无此用户。'); if (!user) throw new ErrorMessage('无此用户。');
let allowedEdit = await user.isAllowedEditBy(res.locals.user); let allowedEdit = await user.isAllowedEditBy(res.locals.user);
@ -156,7 +159,7 @@ app.post('/user/:id/edit', async (req, res) => {
let user; let user;
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
user = await User.fromID(id); user = await User.findById(id);
if (!user) throw new ErrorMessage('无此用户。'); if (!user) throw new ErrorMessage('无此用户。');
let allowedEdit = await user.isAllowedEditBy(res.locals.user); let allowedEdit = await user.isAllowedEditBy(res.locals.user);

8
package.json

@ -4,6 +4,7 @@
"description": "An OnlineJudge System for OI", "description": "An OnlineJudge System for OI",
"main": "app.js", "main": "app.js",
"scripts": { "scripts": {
"prepublish": "tsc -p .",
"start": "node app.js", "start": "node app.js",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
@ -23,8 +24,10 @@
}, },
"homepage": "https://github.com/syzoj/syzoj#readme", "homepage": "https://github.com/syzoj/syzoj#readme",
"dependencies": { "dependencies": {
"@types/fs-extra": "^5.0.5",
"ansi-to-html": "^0.6.10", "ansi-to-html": "^0.6.10",
"async-lock": "^1.2.0", "async-lock": "^1.2.0",
"bluebird": "^3.5.4",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"cheerio": "^1.0.0-rc.1", "cheerio": "^1.0.0-rc.1",
"command-line-args": "^5.1.0", "command-line-args": "^5.1.0",
@ -46,16 +49,17 @@
"moment": "^2.24.0", "moment": "^2.24.0",
"msgpack-lite": "^0.1.26", "msgpack-lite": "^0.1.26",
"multer": "^1.2.0", "multer": "^1.2.0",
"mysql2": "^1.6.5",
"node-7z": "^0.4.0", "node-7z": "^0.4.0",
"nodemailer": "^4.7.0", "nodemailer": "^4.7.0",
"object-assign-deep": "^0.4.0", "object-assign-deep": "^0.4.0",
"object-hash": "^1.3.1", "object-hash": "^1.3.1",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"redis": "^2.8.0", "redis": "^2.8.0",
"reflect-metadata": "^0.1.13",
"request": "^2.74.0", "request": "^2.74.0",
"request-promise": "^4.2.4", "request-promise": "^4.2.4",
"sendmail": "^1.1.1", "sendmail": "^1.1.1",
"sequelize": "^5.1.1",
"serialize-javascript": "^1.6.1", "serialize-javascript": "^1.6.1",
"session-file-store": "^1.0.0", "session-file-store": "^1.0.0",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
@ -64,6 +68,8 @@
"syzoj-renderer": "^1.0.5", "syzoj-renderer": "^1.0.5",
"tempfile": "^2.0.0", "tempfile": "^2.0.0",
"tmp-promise": "^1.0.3", "tmp-promise": "^1.0.3",
"typeorm": "^0.2.16",
"typescript": "^3.4.3",
"uuid": "^3.3.2", "uuid": "^3.3.2",
"waliyun": "^3.1.1", "waliyun": "^3.1.1",
"winston": "^3.2.1", "winston": "^3.2.1",

11
tsconfig.json

@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"preserveConstEnums": true,
"sourceMap": true,
"outDir": "./models-built",
"rootDir": "./models",
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}

609
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save