Menci
6 years ago
45 changed files with 1876 additions and 1866 deletions
@ -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; |
|
@ -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); |
||||||
|
} |
||||||
|
}; |
@ -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; |
|
@ -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(); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
@ -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; |
|
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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; |
|
@ -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; |
|
@ -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; |
||||||
|
} |
@ -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; |
|
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
|
@ -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; |
||||||
|
} |
@ -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; |
|
@ -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; |
||||||
|
} |
@ -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; |
|
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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; |
|
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
|
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
|
@ -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; |
||||||
|
} |
@ -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; |
|
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"module": "commonjs", |
||||||
|
"preserveConstEnums": true, |
||||||
|
"sourceMap": true, |
||||||
|
"outDir": "./models-built", |
||||||
|
"rootDir": "./models", |
||||||
|
"emitDecoratorMetadata": true, |
||||||
|
"experimentalDecorators": true |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue