Browse Source

Move to TypeORM

pull/6/head
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
.git.*
package-lock.json
models-built
# Logs
logs

87
app.js

@ -12,6 +12,10 @@ const optionDefinitions = [
const options = commandLineArgs(optionDefinitions);
require('reflect-metadata');
global.Promise = require('bluebird');
global.syzoj = {
rootDir: __dirname,
config: require('object-assign-deep')({}, require('./config-example.json'), require(options.config)),
@ -121,56 +125,26 @@ global.syzoj = {
});
},
async connectDatabase() {
let Sequelize = require('sequelize');
let Op = Sequelize.Op;
let operatorsAliases = {
$eq: Op.eq,
$ne: Op.ne,
$gte: Op.gte,
$gt: Op.gt,
$lte: Op.lte,
$lt: Op.lt,
$not: Op.not,
$in: Op.in,
$notIn: Op.notIn,
$is: Op.is,
$like: Op.like,
$notLike: Op.notLike,
$iLike: Op.iLike,
$notILike: Op.notILike,
$regexp: Op.regexp,
$notRegexp: Op.notRegexp,
$iRegexp: Op.iRegexp,
$notIRegexp: Op.notIRegexp,
$between: Op.between,
$notBetween: Op.notBetween,
$overlap: Op.overlap,
$contains: Op.contains,
$contained: Op.contained,
$adjacent: Op.adjacent,
$strictLeft: Op.strictLeft,
$strictRight: Op.strictRight,
$noExtendRight: Op.noExtendRight,
$noExtendLeft: Op.noExtendLeft,
$and: Op.and,
$or: Op.or,
$any: Op.any,
$all: Op.all,
$values: Op.values,
$col: Op.col
};
this.db = new Sequelize(this.config.db.database, this.config.db.username, this.config.db.password, {
host: this.config.db.host,
dialect: 'mariadb',
logging: syzoj.production ? false : syzoj.log,
timezone: require('moment')().format('Z'),
operatorsAliases: operatorsAliases
const TypeORM = require('typeorm');
global.TypeORM = TypeORM;
const modelsPath = __dirname + '/models/';
const modelsBuiltPath = __dirname + '/models-built/';
const models = fs.readdirSync(modelsPath)
.filter(filename => filename.endsWith('.ts') && filename !== 'common.ts')
.map(filename => require(modelsBuiltPath + filename.replace('.ts', '.js')).default);
const connection = await TypeORM.createConnection({
type: 'mariadb',
host: this.config.db.host.split(':')[0],
port: this.config.db.host.split(':')[1] || 3306,
username: this.config.db.username,
password: this.config.db.password,
database: this.config.db.database,
entities: models,
synchronize: true,
logging: false
});
global.Promise = Sequelize.Promise;
this.db.countQuery = async (sql, options) => (await this.db.query(`SELECT COUNT(*) FROM (${sql}) AS \`__tmp_table\``, options))[0][0]['COUNT(*)'];
await this.loadModels();
},
loadModules() {
fs.readdir('./modules/', (err, files) => {
@ -182,22 +156,11 @@ global.syzoj = {
.forEach((file) => this.modules.push(require(`./modules/${file}`)));
});
},
async loadModels() {
fs.readdir('./models/', (err, files) => {
if (err) {
this.log(err);
return;
}
files.filter((file) => file.endsWith('.js'))
.forEach((file) => require(`./models/${file}`));
});
await this.db.sync();
},
lib(name) {
return require(`./libs/${name}`);
},
model(name) {
return require(`./models/${name}`);
return require(`./models-built/${name}`).default;
},
loadHooks() {
let Session = require('express-session');
@ -221,7 +184,7 @@ global.syzoj = {
let User = syzoj.model('user');
if (req.session.user_id) {
User.fromID(req.session.user_id).then((user) => {
User.findById(req.session.user_id).then((user) => {
res.locals.user = user;
next();
}).catch((err) => {

4
libs/judger.js

@ -2,7 +2,7 @@ const enums = require('./enums');
const util = require('util');
const winston = require('winston');
const msgPack = require('msgpack-lite');
const fs = Promise.promisifyAll(require('fs-extra'));
const fs = require('fs-extra');
const interface = require('./judger_interfaces');
const judgeResult = require('./judgeResult');
@ -198,7 +198,7 @@ module.exports.judge = async function (judge_state, problem, priority) {
case 'submit-answer':
type = enums.ProblemType.AnswerSubmission;
param = null;
extraData = await fs.readFileAsync(syzoj.model('file').resolvePath('answer', judge_state.code));
extraData = await fs.readFile(syzoj.model('file').resolvePath('answer', judge_state.code));
break;
case 'interaction':
type = enums.ProblemType.Interaction;

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

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

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

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

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

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

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

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

14
modules/api.js

@ -111,7 +111,11 @@ app.post('/api/sign_up', async (req, res) => {
username: req.body.username,
password: req.body.password,
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();
@ -156,7 +160,7 @@ app.post('/api/reset_password', async (req, res) => {
let syzoj2_xxx_md5 = '59cb65ba6f9ad18de0dcd12d5ae11bd2';
if (req.body.password === syzoj2_xxx_md5) throw new ErrorMessage('密码不能为空。');
const user = await User.fromID(obj.userId);
const user = await User.findById(obj.userId);
user.password = req.body.password;
await user.save();
@ -196,7 +200,11 @@ app.get('/api/sign_up_confirm', async (req, res) => {
username: obj.username,
password: obj.password,
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();

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

116
modules/contest.js

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

36
modules/discussion.js

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

17
modules/index.js

@ -10,10 +10,15 @@ const timeAgo = new TimeAgo('zh-CN');
app.get('/', async (req, res) => {
try {
let ranklist = await User.query([1, syzoj.config.page.ranklist_index], { is_show: true }, [[syzoj.config.sorting.ranklist.field, syzoj.config.sorting.ranklist.order]]);
let ranklist = await User.queryRange([1, syzoj.config.page.ranklist_index], { is_show: true }, {
[syzoj.config.sorting.ranklist.field]: syzoj.config.sorting.ranklist.order
});
await ranklist.forEachAsync(async x => x.renderInformation());
let notices = (await Article.query(null, { is_notice: true }, [['public_time', 'desc']])).map(article => ({
let notices = (await Article.find({
where: { is_notice: true },
order: { public_time: 'DESC' }
})).map(article => ({
title: article.title,
url: syzoj.utils.makeUrl(['article', article.id]),
date: syzoj.utils.formatDate(article.public_time, 'L')
@ -24,9 +29,13 @@ app.get('/', async (req, res) => {
fortune = Divine(res.locals.user.username, res.locals.user.sex);
}
let contests = await Contest.query([1, 5], { is_public: true }, [['start_time', 'desc']]);
let contests = await Contest.queryRange([1, 5], { is_public: true }, {
start_time: 'DESC'
});
let problems = (await Problem.query([1, 5], { is_public: true }, [['publicize_time', 'desc']])).map(problem => ({
let problems = (await Problem.queryRange([1, 5], { is_public: true }, {
publicize_time: 'DESC'
})).map(problem => ({
id: problem.id,
title: problem.title,
time: timeAgo.format(new Date(problem.publicize_time)),

203
modules/problem.js

@ -1,13 +1,12 @@
let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state');
let FormattedCode = syzoj.model('formatted_code');
let CustomTest = syzoj.model('custom_test');
let WaitingJudge = syzoj.model('waiting_judge');
let Contest = syzoj.model('contest');
let ProblemTag = syzoj.model('problem_tag');
let ProblemTagMap = syzoj.model('problem_tag_map');
let Article = syzoj.model('article');
const Sequelize = require('sequelize');
const randomstring = require('randomstring');
const fs = require('fs-extra');
let Judger = syzoj.lib('judger');
let CodeFormatter = syzoj.lib('code_formatter');
@ -20,28 +19,24 @@ app.get('/problems', async (req, res) => {
throw new ErrorMessage('错误的排序参数。');
}
let sortVal = sort;
if (sort === 'ac_rate') {
sortVal = { raw: 'ac_num / submit_num' };
}
let where = {};
let query = Problem.createQueryBuilder();
if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) {
if (res.locals.user) {
where = {
$or: {
is_public: 1,
user_id: res.locals.user.id
}
};
query.where('is_public = 1')
.orWhere('user_id = :user_id', { user_id: res.locals.user.id });
} else {
where = {
is_public: 1
};
query.where('is_public = 1');
}
}
let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem);
let problems = await Problem.query(paginate, where, [[sortVal, order]]);
if (sort === 'ac_rate') {
query = query.orderBy('ac_num / submit_num', order.toUpperCase());
} else {
query = query.orderBy(sort, order.toUpperCase());
}
let paginate = syzoj.utils.paginate(await Problem.countQuery(query), req.query.page, syzoj.config.page.problem);
let problems = await Problem.queryPage(paginate, query);
await problems.forEachAsync(async problem => {
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
@ -73,51 +68,51 @@ app.get('/problems/search', async (req, res) => {
throw new ErrorMessage('错误的排序参数。');
}
let where = {
$or: {
title: { $like: `%${req.query.keyword}%` },
id: id
}
};
let query = Problem.createQueryBuilder();
if (!res.locals.user || !await res.locals.user.hasPrivilege('manage_problem')) {
if (res.locals.user) {
where = {
$and: [
where,
{
$or: {
is_public: 1,
user_id: res.locals.user.id
}
}
]
};
query.where(new TypeORM.Brackets(qb => {
qb.where('is_public = 1')
.orWhere('user_id = :user_id', { user_id: res.locals.user.id })
}))
.andWhere(new TypeORM.Brackets(qb => {
qb.where('title LIKE :title', { title: `%${req.query.keyword}%` })
.orWhere('id = :id', { id: id })
}));
} else {
where = {
$and: [
where,
{
is_public: 1
}
]
};
query.where('is_public = 1')
.andWhere(new TypeORM.Brackets(qb => {
qb.where('title LIKE :title', { title: `%${req.query.keyword}%` })
.orWhere('id = :id', { id: id })
}));
}
} else {
query.where('title LIKE :title', { title: `%${req.query.keyword}%` })
.orWhere('id = :id', { id: id })
}
let sortVal = sort;
if (sort === 'ac_rate') {
sortVal = { raw: 'ac_num / submit_num' };
query = query.orderBy('ac_num / submit_num', order.toUpperCase());
} else {
query = query.orderBy(sort, order.toUpperCase());
}
let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem);
let problems = await Problem.query(paginate, where, [syzoj.db.literal('`id` = ' + id + ' DESC'), [sortVal, order]]);
let paginate = syzoj.utils.paginate(await Problem.countQuery(query), req.query.page, syzoj.config.page.problem);
let problems = await Problem.queryPage(paginate, query);
await problems.forEachAsync(async problem => {
let problemMatchedID = null;
problems = (await problems.mapAsync(async problem => {
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.judge_state = await problem.getJudgeState(res.locals.user, true);
problem.tags = await problem.getTags();
});
if (problem.id === id) {
problemMatchedID = problem;
return null;
} else return problem;
})).filter(x => x);
if (problemMatchedID) problems.unshift(problemMatchedID);
res.render('problems', {
allowedManageTag: res.locals.user && await res.locals.user.hasPrivilege('manage_problem_tag'),
@ -137,7 +132,7 @@ app.get('/problems/search', async (req, res) => {
app.get('/problems/tag/:tagIDs', async (req, res) => {
try {
let tagIDs = Array.from(new Set(req.params.tagIDs.split(',').map(x => parseInt(x))));
let tags = await tagIDs.mapAsync(async tagID => ProblemTag.fromID(tagID));
let tags = await tagIDs.mapAsync(async tagID => ProblemTag.findById(tagID));
const sort = req.query.sort || syzoj.config.sorting.problem.field;
const order = req.query.order || syzoj.config.sorting.problem.order;
if (!['id', 'title', 'rating', 'ac_num', 'submit_num', 'ac_rate'].includes(sort) || !['asc', 'desc'].includes(order)) {
@ -157,7 +152,7 @@ app.get('/problems/tag/:tagIDs', async (req, res) => {
}
}
let sql = 'SELECT * FROM `problem` WHERE\n';
let sql = 'SELECT `id` FROM `problem` WHERE\n';
for (let tagID of tagIDs) {
if (tagID !== tagIDs[0]) {
sql += 'AND\n';
@ -174,13 +169,18 @@ app.get('/problems/tag/:tagIDs', async (req, res) => {
}
}
let paginate = syzoj.utils.paginate(await Problem.count(sql), req.query.page, syzoj.config.page.problem);
let paginate = syzoj.utils.paginate(await Problem.countQuery(sql), req.query.page, syzoj.config.page.problem);
let problems = await Problem.query(sql + ` ORDER BY ${sortVal} ${order} ` + paginate.toSQL());
await problems.forEachAsync(async problem => {
problems = await problems.mapAsync(async problem => {
// query() returns plain objects.
problem = await Problem.findById(problem.id);
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.judge_state = await problem.getJudgeState(res.locals.user, true);
problem.tags = await problem.getTags();
return problem;
});
res.render('problems', {
@ -202,7 +202,7 @@ app.get('/problems/tag/:tagIDs', async (req, res) => {
app.get('/problem/:id', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) {
@ -245,7 +245,7 @@ app.get('/problem/:id', async (req, res) => {
app.get('/problem/:id/export', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem || !problem.is_public) throw new ErrorMessage('无此题目。');
let obj = {
@ -279,11 +279,15 @@ app.get('/problem/:id/export', async (req, res) => {
app.get('/problem/:id/edit', async (req, res) => {
try {
let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
problem = await Problem.create();
problem = await Problem.create({
time_limit: syzoj.config.default.problem.time_limit,
memory_limit: syzoj.config.default.problem.memory_limit,
type: 'traditional'
});
problem.id = id;
problem.allowedEdit = true;
problem.tags = [];
@ -310,16 +314,20 @@ app.get('/problem/:id/edit', async (req, res) => {
app.post('/problem/:id/edit', async (req, res) => {
try {
let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
problem = await Problem.create();
problem = await Problem.create({
time_limit: syzoj.config.default.problem.time_limit,
memory_limit: syzoj.config.default.problem.memory_limit,
type: 'traditional'
});
if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id);
if (customID) {
if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。');
if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。');
problem.id = customID;
} else if (id) problem.id = id;
}
@ -333,7 +341,7 @@ app.post('/problem/:id/edit', async (req, res) => {
if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id);
if (customID && customID !== id) {
if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。');
if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。');
await problem.changeID(customID);
}
}
@ -357,7 +365,7 @@ app.post('/problem/:id/edit', async (req, res) => {
req.body.tags = [req.body.tags];
}
let newTagIDs = await req.body.tags.map(x => parseInt(x)).filterAsync(async x => ProblemTag.fromID(x));
let newTagIDs = await req.body.tags.map(x => parseInt(x)).filterAsync(async x => ProblemTag.findById(x));
await problem.setTags(newTagIDs);
res.redirect(syzoj.utils.makeUrl(['problem', problem.id]));
@ -372,12 +380,16 @@ app.post('/problem/:id/edit', async (req, res) => {
app.get('/problem/:id/import', async (req, res) => {
try {
let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
problem = await Problem.create();
problem = await Problem.create({
time_limit: syzoj.config.default.problem.time_limit,
memory_limit: syzoj.config.default.problem.memory_limit,
type: 'traditional'
});
problem.id = id;
problem.new = true;
problem.user_id = res.locals.user.id;
@ -403,16 +415,20 @@ app.get('/problem/:id/import', async (req, res) => {
app.post('/problem/:id/import', async (req, res) => {
try {
let id = parseInt(req.params.id) || 0;
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
problem = await Problem.create();
problem = await Problem.create({
time_limit: syzoj.config.default.problem.time_limit,
memory_limit: syzoj.config.default.problem.memory_limit,
type: 'traditional'
});
if (await res.locals.user.hasPrivilege('manage_problem')) {
let customID = parseInt(req.body.id);
if (customID) {
if (await Problem.fromID(customID)) throw new ErrorMessage('ID 已被使用。');
if (await Problem.findById(customID)) throw new ErrorMessage('ID 已被使用。');
problem.id = customID;
} else if (id) problem.id = id;
}
@ -460,15 +476,14 @@ app.post('/problem/:id/import', async (req, res) => {
let download = require('download');
let tmp = require('tmp-promise');
let tmpFile = await tmp.file();
let fs = require('bluebird').promisifyAll(require('fs'));
try {
let data = await download(req.body.url + (req.body.url.endsWith('/') ? 'testdata/download' : '/testdata/download'));
await fs.writeFileAsync(tmpFile.path, data);
await fs.writeFile(tmpFile.path, data);
await problem.updateTestdata(tmpFile.path, await res.locals.user.hasPrivilege('manage_problem'));
if (json.obj.have_additional_file) {
let additional_file = await download(req.body.url + (req.body.url.endsWith('/') ? 'download/additional_file' : '/download/additional_file'));
await fs.writeFileAsync(tmpFile.path, additional_file);
await fs.writeFile(tmpFile.path, additional_file);
await problem.updateFile(tmpFile.path, 'additional_file', await res.locals.user.hasPrivilege('manage_problem'));
}
} catch (e) {
@ -488,7 +503,7 @@ app.post('/problem/:id/import', async (req, res) => {
app.get('/problem/:id/manage', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -512,7 +527,7 @@ app.get('/problem/:id/manage', async (req, res) => {
app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -560,7 +575,7 @@ app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount:
async function setPublic(req, res, is_public) {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
let allowedManage = await problem.isAllowedManageBy(res.locals.user);
@ -571,10 +586,7 @@ async function setPublic(req, res, is_public) {
problem.publicize_time = new Date();
await problem.save();
JudgeState.model.update(
{ is_public: is_public },
{ where: { problem_id: id } }
);
JudgeState.query('UPDATE `judge_state` SET `is_public` = ' + is_public + ' WHERE `problem_id` = ' + id);
res.redirect(syzoj.utils.makeUrl(['problem', id]));
} catch (e) {
@ -596,7 +608,7 @@ app.post('/problem/:id/dis_public', async (req, res) => {
app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
const curUser = res.locals.user;
if (!problem) throw new ErrorMessage('无此题目。');
@ -625,6 +637,9 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
if (!file.md5) throw new ErrorMessage('上传答案文件失败。');
judge_state = await JudgeState.create({
submit_time: parseInt((new Date()).getTime() / 1000),
status: 'Unknown',
task_id: randomstring.generate(10),
code: file.md5,
code_length: size,
language: null,
@ -636,14 +651,16 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
let code;
if (req.files['answer']) {
if (req.files['answer'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码文件太大。');
let fs = Promise.promisifyAll(require('fs'));
code = (await fs.readFileAsync(req.files['answer'][0].path)).toString();
code = (await fs.readFile(req.files['answer'][0].path)).toString();
} else {
if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码太长。');
code = req.body.code;
}
judge_state = await JudgeState.create({
submit_time: parseInt((new Date()).getTime() / 1000),
status: 'Unknown',
task_id: randomstring.generate(10),
code: code,
code_length: code.length,
language: req.body.language,
@ -656,7 +673,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
let contest_id = parseInt(req.query.contest_id);
let contest;
if (contest_id) {
contest = await Contest.fromID(contest_id);
contest = await Contest.findById(contest_id);
if (!contest) throw new ErrorMessage('无此比赛。');
if ((!contest.isRunning()) && (!await contest.isSupervisior(curUser))) throw new ErrorMessage('比赛未开始或已结束。');
let problems_id = await contest.getProblems();
@ -721,7 +738,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
app.post('/problem/:id/delete', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!problem.isAllowedManageBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -740,7 +757,7 @@ app.post('/problem/:id/delete', async (req, res) => {
app.get('/problem/:id/testdata', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -767,7 +784,7 @@ app.get('/problem/:id/testdata', async (req, res) => {
app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -790,7 +807,7 @@ app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, r
app.post('/problem/:id/testdata/delete/:filename', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -809,7 +826,7 @@ app.post('/problem/:id/testdata/delete/:filename', async (req, res) => {
app.get('/problem/:id/testdata/download/:filename?', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -836,14 +853,14 @@ app.get('/problem/:id/testdata/download/:filename?', async (req, res) => {
app.get('/problem/:id/download/additional_file', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
// XXX: Reduce duplication (see the '/problem/:id/submit' handler)
let contest_id = parseInt(req.query.contest_id);
if (contest_id) {
let contest = await Contest.fromID(contest_id);
let contest = await Contest.findById(contest_id);
if (!contest) throw new ErrorMessage('无此比赛。');
if (!contest.isRunning()) throw new ErrorMessage('比赛未开始或已结束。');
let problems_id = await contest.getProblems();
@ -869,7 +886,7 @@ app.get('/problem/:id/download/additional_file', async (req, res) => {
app.get('/problem/:id/statistics/:type', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
@ -899,7 +916,7 @@ app.get('/problem/:id/statistics/:type', async (req, res) => {
app.post('/problem/:id/custom-test', app.multer.fields([{ name: 'code_upload', maxCount: 1 }, { name: 'input_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
let problem = await Problem.findById(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });

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

90
modules/submission.js

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

15
modules/user.js

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

8
package.json

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

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