mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
Hello airbnb
This commit is contained in:
1 parent
f9468536de
commit
8c195661d7
78 files changed
+2032
-1471
No files matched your search
@@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
commonjs: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
"indent": ["warn", 4],
|
||||
"no-plusplus": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"no-console": "off",
|
||||
"no-extend-native": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"max-classes-per-file": "off",
|
||||
"radix": "off",
|
||||
"guard-for-in": "off",
|
||||
"no-param-reassign": "off",
|
||||
"global-require": "off"
|
||||
},
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
4
|
||||
],
|
||||
"linebreak-style": [
|
||||
"warn",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"no-unused-vars": "warn",
|
||||
"no-unreachable": "warn",
|
||||
"require-atomic-updates": "off"
|
||||
}
|
||||
}
|
||||
+15
-14
@@ -1,28 +1,21 @@
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', async input => {
|
||||
process.stdin.on('data', async (input) => {
|
||||
try {
|
||||
let t = eval(input.toString().trim());
|
||||
const t = eval(input.toString().trim()); // eslint-disable-line no-eval
|
||||
if (t instanceof Promise) console.log(await t);
|
||||
else console.log(t);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
process.on('restart', async () => {
|
||||
console.log('Signal detected, restarting...');
|
||||
await global.Hydro.stop();
|
||||
await global.Hydro.destory();
|
||||
delete global.Hydro;
|
||||
delete require.cache;
|
||||
run();
|
||||
});
|
||||
const path = require('path');
|
||||
const EventEmitter = require('events');
|
||||
const i18n = require('./lib/i18n');
|
||||
|
||||
i18n(path.resolve(__dirname, '..', 'locales', 'zh_CN.yaml'), 'zh_CN');
|
||||
i18n(path.resolve(__dirname, '..', 'locales', 'zh_TW.yaml'), 'zh_TW');
|
||||
i18n(path.resolve(__dirname, '..', 'locales', 'en.yaml'), 'en');
|
||||
|
||||
const EventEmitter = require('events');
|
||||
global.bus = new EventEmitter();
|
||||
async function run() {
|
||||
require('./utils');
|
||||
@@ -34,7 +27,7 @@ async function run() {
|
||||
});
|
||||
});
|
||||
require('./service/gridfs');
|
||||
let server = require('./service/server');
|
||||
const server = require('./service/server');
|
||||
require('./handler/home');
|
||||
require('./handler/problem');
|
||||
require('./handler/record');
|
||||
@@ -43,7 +36,15 @@ async function run() {
|
||||
require('./handler/contest');
|
||||
server.start();
|
||||
}
|
||||
run().catch(e => {
|
||||
process.on('restart', async () => {
|
||||
console.log('Signal detected, restarting...');
|
||||
await global.Hydro.stop();
|
||||
await global.Hydro.destory();
|
||||
delete global.Hydro;
|
||||
delete require.cache;
|
||||
run();
|
||||
});
|
||||
run().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
+29
-15
@@ -2,7 +2,7 @@ class UserFacingError extends Error {
|
||||
constructor(type) {
|
||||
super(type);
|
||||
this.code = 500;
|
||||
//this.stack = '';
|
||||
// this.stack = '';
|
||||
this.params = [];
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,9 @@ class VerifyPasswordError extends ForbiddenError {
|
||||
}
|
||||
}
|
||||
class OpcountExceededError extends ForbiddenError {
|
||||
constructor(op, period_secs, max_operations) {
|
||||
constructor(op, periodSecs, maxOperations) {
|
||||
super('OpcountExceededError');
|
||||
this.params = [op, period_secs, max_operations];
|
||||
this.params = [op, periodSecs, maxOperations];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,15 +155,31 @@ class ProblemDataNotFoundError extends NotFoundError {
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
BadRequestError, ForbiddenError, NotFoundError,
|
||||
LoginError, UserAlreadyExistError, InvalidTokenError,
|
||||
UserNotFoundError, VerifyPasswordError, ProblemDataNotFoundError,
|
||||
OpcountExceededError, PermissionError, NoProblemError,
|
||||
ValidationError, ProblemNotFoundError, TrainingNotFoundError,
|
||||
ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError,
|
||||
AlreadyVotedError, ContestNotAttendedError, ContestNotLiveError,
|
||||
ContestScoreboardHiddenError, ContestAlreadyAttendedError, UserFacingError,
|
||||
SystemError
|
||||
BadRequestError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
LoginError,
|
||||
UserAlreadyExistError,
|
||||
InvalidTokenError,
|
||||
UserNotFoundError,
|
||||
VerifyPasswordError,
|
||||
ProblemDataNotFoundError,
|
||||
OpcountExceededError,
|
||||
PermissionError,
|
||||
NoProblemError,
|
||||
ValidationError,
|
||||
ProblemNotFoundError,
|
||||
TrainingNotFoundError,
|
||||
ContestNotFoundError,
|
||||
RecordNotFoundError,
|
||||
SolutionNotFoundError,
|
||||
AlreadyVotedError,
|
||||
ContestNotAttendedError,
|
||||
ContestNotLiveError,
|
||||
ContestScoreboardHiddenError,
|
||||
ContestAlreadyAttendedError,
|
||||
UserFacingError,
|
||||
SystemError,
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -201,7 +217,6 @@ class UserFacingError(Error):
|
||||
return 'An error has occurred.'
|
||||
|
||||
|
||||
|
||||
class BlacklistedError(ForbiddenError):
|
||||
@property
|
||||
def message(self):
|
||||
@@ -240,7 +255,6 @@ class InvalidOperationError(ForbiddenError):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class InvalidTokenDigestError(ForbiddenError):
|
||||
pass
|
||||
|
||||
@@ -374,4 +388,4 @@ class SendMailError(UserFacingError):
|
||||
@property
|
||||
def message(self):
|
||||
return 'Failed to send mail to {0}.'
|
||||
*/
|
||||
*/
|
||||
+114
-78
@@ -1,20 +1,23 @@
|
||||
const
|
||||
{ ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||
ContestNotAttendedError } = require('../error'),
|
||||
{ PERM_CREATE_CONTEST, PERM_EDIT_CONTEST } = require('../permission'),
|
||||
{ constants } = require('../options'),
|
||||
paginate = require('../lib/paginate'),
|
||||
contest = require('../model/contest'),
|
||||
problem = require('../model/problem'),
|
||||
record = require('../model/record'),
|
||||
user = require('../model/user'),
|
||||
{ Route, Handler } = require('../service/server');
|
||||
const {
|
||||
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||
ContestNotAttendedError,
|
||||
} = require('../error');
|
||||
const { PERM_CREATE_CONTEST, PERM_EDIT_CONTEST } = require('../permission');
|
||||
const { constants } = require('../options');
|
||||
const paginate = require('../lib/paginate');
|
||||
const contest = require('../model/contest');
|
||||
const problem = require('../model/problem');
|
||||
const record = require('../model/record');
|
||||
const user = require('../model/user');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
|
||||
const ContestHandler = contest.ContestHandlerMixin(Handler);
|
||||
class ContestListHandler extends ContestHandler {
|
||||
async get({ rule = 0, page = 1 }) {
|
||||
this.response.template = 'contest_main.html';
|
||||
let tdocs, qs, tpcount;
|
||||
let tdocs;
|
||||
let qs;
|
||||
let tpcount;
|
||||
if (!rule) {
|
||||
tdocs = contest.getMulti().sort({ beginAt: -1 });
|
||||
qs = '';
|
||||
@@ -23,12 +26,13 @@ class ContestListHandler extends ContestHandler {
|
||||
tdocs = contest.getMulti({ rule }).sort({ beginAt: -1 });
|
||||
qs = 'rule={0}'.format(rule);
|
||||
}
|
||||
// eslint-disable-next-line prefer-const
|
||||
[tdocs, tpcount] = await paginate(tdocs, page, constants.CONTEST_PER_PAGE);
|
||||
let tids = [];
|
||||
for (let tdoc of tdocs) tids.push(tdoc._id);
|
||||
let tsdict = await contest.getListStatus(this.user._id, tids);
|
||||
const tids = [];
|
||||
for (const tdoc of tdocs) tids.push(tdoc._id);
|
||||
const tsdict = await contest.getListStatus(this.user._id, tids);
|
||||
this.response.body = {
|
||||
page, tpcount, qs, rule, tdocs, tsdict
|
||||
page, tpcount, qs, rule, tdocs, tsdict,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -36,35 +40,35 @@ class ContestDetailHandler extends ContestHandler {
|
||||
async _prepare({ tid }) {
|
||||
this.tdoc = await contest.get(tid);
|
||||
}
|
||||
|
||||
async get({ page = 1 }) {
|
||||
this.response.template = 'contest_detail.html';
|
||||
let [tsdoc, pdict] = await Promise.all([
|
||||
const [tsdoc, pdict] = await Promise.all([
|
||||
contest.getStatus(this.tdoc._id, this.user._id),
|
||||
problem.getList(this.tdoc.pids)
|
||||
problem.getList(this.tdoc.pids),
|
||||
]);
|
||||
let psdict = {}, rdict = {}, attended;
|
||||
const psdict = {}; let rdict = {}; let
|
||||
attended;
|
||||
if (tsdoc) {
|
||||
attended = tsdoc.attend == 1;
|
||||
for (let pdetail of tsdoc.journal || [])
|
||||
psdict[pdetail.pid] = pdetail;
|
||||
attended = tsdoc.attend === 1;
|
||||
for (const pdetail of tsdoc.journal || []) { psdict[pdetail.pid] = pdetail; }
|
||||
if (this.canShowRecord(this.tdoc)) {
|
||||
let q = [];
|
||||
for (let i in psdict) q.push(psdict[i].rid);
|
||||
const q = [];
|
||||
for (const i in psdict) q.push(psdict[i].rid);
|
||||
rdict = await record.getList(q);
|
||||
} else
|
||||
for (let i in psdict)
|
||||
rdict[psdict[i].rid] = { _id: psdict[i].rid };
|
||||
} else { for (const i in psdict) rdict[psdict[i].rid] = { _id: psdict[i].rid }; }
|
||||
} else attended = false;
|
||||
let udict = await user.getList([this.tdoc.owner]);
|
||||
let path = [
|
||||
const udict = await user.getList([this.tdoc.owner]);
|
||||
const path = [
|
||||
['contest_main', '/c'],
|
||||
[this.tdoc.title, null, true]
|
||||
[this.tdoc.title, null, true],
|
||||
];
|
||||
this.response.body = {
|
||||
path, tdoc: this.tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page
|
||||
path, tdoc: this.tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page,
|
||||
};
|
||||
}
|
||||
async post_attend() {
|
||||
|
||||
async postAttend() {
|
||||
if (contest.is_done(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id);
|
||||
await contest.attend(this.tdoc._id, this.user._id);
|
||||
this.back();
|
||||
@@ -72,108 +76,136 @@ class ContestDetailHandler extends ContestHandler {
|
||||
}
|
||||
class ContestScoreboardHandler extends ContestDetailHandler {
|
||||
async get({ tid }) {
|
||||
let [tdoc, rows, udict] = await this.getScoreboard(tid);
|
||||
let path = [
|
||||
const [tdoc, rows, udict] = await this.getScoreboard(tid);
|
||||
const path = [
|
||||
['contest_main', '/c'],
|
||||
[tdoc.title, `/c/${tid}`, true],
|
||||
['contest_scoreboard', null]
|
||||
['contest_scoreboard', null],
|
||||
];
|
||||
this.response.template = 'contest_scoreboard.html';
|
||||
this.response.body = { tdoc, rows, path, udict };
|
||||
this.response.body = {
|
||||
tdoc, rows, path, udict,
|
||||
};
|
||||
}
|
||||
}
|
||||
class ContestEditHandler extends ContestDetailHandler {
|
||||
async prepare({ tid }) {
|
||||
let tdoc = await contest.get(tid);
|
||||
if (!tdoc.owner != this.user._id) this.checkPerm(PERM_EDIT_CONTEST);
|
||||
const tdoc = await contest.get(tid);
|
||||
if (!tdoc.owner !== this.user._id) this.checkPerm(PERM_EDIT_CONTEST);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'contest_edit.html';
|
||||
let rules = {};
|
||||
for (let i in contest.RULES)
|
||||
rules[i] = contest.RULES[i].TEXT;
|
||||
let duration = (this.tdoc.endAt.getTime() - this.tdoc.beginAt.getTime()) / 3600 / 1000;
|
||||
let path = [
|
||||
const rules = {};
|
||||
for (const i in contest.RULES) { rules[i] = contest.RULES[i].TEXT; }
|
||||
const duration = (this.tdoc.endAt.getTime() - this.tdoc.beginAt.getTime()) / 3600 / 1000;
|
||||
const path = [
|
||||
['contest_main', '/c'],
|
||||
[this.tdoc.title, `/c/${this.tdoc._id}`, true],
|
||||
['contest_edit', null]
|
||||
['contest_edit', null],
|
||||
];
|
||||
let dt = this.tdoc.beginAt;
|
||||
const dt = this.tdoc.beginAt;
|
||||
this.response.body = {
|
||||
rules, tdoc: this.tdoc, duration, path, pids: this.tdoc.pids.join(','),
|
||||
rules,
|
||||
tdoc: this.tdoc,
|
||||
duration,
|
||||
path,
|
||||
pids: this.tdoc.pids.join(','),
|
||||
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
|
||||
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
|
||||
page_name: 'contest_edit'
|
||||
page_name: 'contest_edit',
|
||||
};
|
||||
}
|
||||
async post({ beginAtDate, beginAtTime, duration, title, content, rule, pids }) {
|
||||
let beginAt, endAt;
|
||||
|
||||
async post({
|
||||
beginAtDate, beginAtTime, duration, title, content, rule, pids,
|
||||
}) {
|
||||
let beginAt;
|
||||
try {
|
||||
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
|
||||
} catch (e) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
}
|
||||
endAt = new Date(beginAt + duration * 3600 * 1000);
|
||||
const endAt = new Date(beginAt + duration * 3600 * 1000);
|
||||
if (beginAt >= endAt) throw new ValidationError('duration');
|
||||
pids = await this.verifyProblems(pids);
|
||||
await contest.edit(this.tdoc._id, title, content, rule, beginAt, endAt, pids);
|
||||
if (this.tdoc.beginAt != beginAt || this.tdoc.endAt != endAt
|
||||
|| Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule != rule)
|
||||
if (this.tdoc.beginAt !== beginAt || this.tdoc.endAt !== endAt
|
||||
|| Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule !== rule) {
|
||||
await contest.recalcStatus(this.tdoc._id);
|
||||
}
|
||||
if (this.preferJson) this.response.body = { tid: this.tdoc._id };
|
||||
else this.response.redirect = `/c/${this.tdoc._id}`;
|
||||
}
|
||||
}
|
||||
class ContestProblemHandler extends ContestDetailHandler {
|
||||
async prepare({ tid, pid }) {
|
||||
[this.tdoc, this.pdoc] = await Promise.all([contest.get(tid), problem.get(pid, this.user._id)]);
|
||||
[this.tdoc, this.pdoc] = await Promise.all([
|
||||
contest.get(tid),
|
||||
problem.get(pid, this.user._id),
|
||||
]);
|
||||
[this.tsdoc, this.udoc] = await Promise.all([
|
||||
contest.getStatus(this.tdoc._id, this.user._id),
|
||||
user.getById(this.tdoc.owner)
|
||||
user.getById(this.tdoc.owner),
|
||||
]);
|
||||
this.attended = this.tsdoc && this.tsdoc.attend == 1;
|
||||
this.attended = this.tsdoc && this.tsdoc.attend === 1;
|
||||
this.response.template = 'problem_detail.html';
|
||||
if (contest.is_done(this.tdoc)) {
|
||||
if (!this.attended) throw new ContestNotAttendedError(this.tdoc._id);
|
||||
if (!contest.is_ongoing(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id);
|
||||
}
|
||||
console.log(this.tdoc.pids, this.pdoc._id);
|
||||
if (!this.tdoc.pids.map(s => s.toString()).includes(this.pdoc._id.toString()))
|
||||
if (!this.tdoc.pids.map((s) => s.toString()).includes(this.pdoc._id.toString())) {
|
||||
throw new ProblemNotFoundError(pid, this.tdoc._id);
|
||||
}
|
||||
}
|
||||
|
||||
async get({ tid }) {
|
||||
let path = [
|
||||
const path = [
|
||||
['contest_main', '/c'],
|
||||
[this.tdoc.title, `/c/${tid}`, true],
|
||||
[this.pdoc.title, null, true]
|
||||
[this.pdoc.title, null, true],
|
||||
];
|
||||
this.response.body = {
|
||||
tdoc: this.tdoc, pdoc: this.pdoc, tsdoc: this.tsdoc,
|
||||
udoc: this.udoc, attended: this.attended, path
|
||||
tdoc: this.tdoc,
|
||||
pdoc: this.pdoc,
|
||||
tsdoc: this.tsdoc,
|
||||
udoc: this.udoc,
|
||||
attended: this.attended,
|
||||
path,
|
||||
};
|
||||
}
|
||||
}
|
||||
class ContestProblemSubmitHandler extends ContestProblemHandler {
|
||||
async get({ tid, pid }) {
|
||||
let rdocs = [];
|
||||
if (this.canShowRecord(this.tdoc))
|
||||
if (this.canShowRecord(this.tdoc)) {
|
||||
rdocs = await record.getUserInProblemMulti(this.user._id, this.pdoc._id, true)
|
||||
.sort({ _id: -1 }).limit(10).toArray();
|
||||
}
|
||||
this.response.template = 'problem_submit.html';
|
||||
let path = [
|
||||
const path = [
|
||||
['contest_main', '/c'],
|
||||
[this.tdoc.title, `/c/${tid}`, true],
|
||||
[this.pdoc.title, `/c/${tid}/p/${pid}`, true],
|
||||
['contest_detail_problem_submit', null]
|
||||
['contest_detail_problem_submit', null],
|
||||
];
|
||||
this.response.body = {
|
||||
tdoc: this.tdoc, pdoc: this.pdoc, tsdoc: this.tsdoc,
|
||||
udoc: this.udoc, attended: this.attend, path, rdocs
|
||||
tdoc: this.tdoc,
|
||||
pdoc: this.pdoc,
|
||||
tsdoc: this.tsdoc,
|
||||
udoc: this.udoc,
|
||||
attended: this.attend,
|
||||
path,
|
||||
rdocs,
|
||||
};
|
||||
}
|
||||
|
||||
async post({ tid, lang, code }) {
|
||||
await this.limitRate('add_record', 60, 100);
|
||||
let rid = await record.add({ pid: this.pdoc._id, uid: this.user._id, lang, code, tid: this.tdoc._id, hidden: true });
|
||||
const rid = await record.add({
|
||||
pid: this.pdoc._id, uid: this.user._id, lang, code, tid: this.tdoc._id, hidden: true,
|
||||
});
|
||||
await contest.updateStatus(this.tdoc._id, this.user._id, rid, this.pdoc._id, false, 0);
|
||||
if (!this.canShowRecord(this.tdoc)) {
|
||||
this.response.body = { tid };
|
||||
@@ -188,33 +220,37 @@ class ContestCreateHandler extends ContestHandler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_CREATE_CONTEST);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'contest_edit.html';
|
||||
let rules = {};
|
||||
for (let i in contest.RULES)
|
||||
rules[i] = contest.RULES[i].TEXT;
|
||||
let now = new Date();
|
||||
const rules = {};
|
||||
for (const i in contest.RULES) { rules[i] = contest.RULES[i].TEXT; }
|
||||
const now = new Date();
|
||||
let ts = now.getTime();
|
||||
ts = ts - ts % (15 * 60 * 1000) + 15 * 60 * 1000;
|
||||
let dt = new Date(ts);
|
||||
ts = ts - (ts % (15 * 60 * 1000)) + 15 * 60 * 1000;
|
||||
const dt = new Date(ts);
|
||||
this.response.body = {
|
||||
rules, page_name: 'contest_create',
|
||||
rules,
|
||||
page_name: 'contest_create',
|
||||
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
|
||||
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
|
||||
pids: '1000, 1001'
|
||||
pids: '1000, 1001',
|
||||
};
|
||||
}
|
||||
async post({ title, content, rule, beginAtDate, beginAtTime, duration, pids }) {
|
||||
let beginAt, endAt;
|
||||
|
||||
async post({
|
||||
title, content, rule, beginAtDate, beginAtTime, duration, pids,
|
||||
}) {
|
||||
let beginAt;
|
||||
try {
|
||||
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
|
||||
} catch (e) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
}
|
||||
endAt = new Date(beginAt.getTime() + duration * 3600 * 1000);
|
||||
const endAt = new Date(beginAt.getTime() + duration * 3600 * 1000);
|
||||
if (beginAt >= endAt) throw new ValidationError('duration');
|
||||
pids = await this.verifyProblems(pids);
|
||||
let tid = await contest.add(title, content, this.user._id, rule, beginAt, endAt, pids);
|
||||
const tid = await contest.add(title, content, this.user._id, rule, beginAt, endAt, pids);
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = `/c/${tid}`;
|
||||
}
|
||||
|
||||
+33
-25
@@ -1,16 +1,17 @@
|
||||
const
|
||||
{ PERM_JUDGE } = require('../permission'),
|
||||
record = require('../model/record'),
|
||||
problem = require('../model/problem'),
|
||||
bus = require('../service/bus'),
|
||||
queue = require('../service/queue'),
|
||||
{ Route, Handler, Connection, ConnectionHandler } = require('../service/server');
|
||||
const { PERM_JUDGE } = require('../permission');
|
||||
const record = require('../model/record');
|
||||
const problem = require('../model/problem');
|
||||
const bus = require('../service/bus');
|
||||
const queue = require('../service/queue');
|
||||
const {
|
||||
Route, Handler, Connection, ConnectionHandler,
|
||||
} = require('../service/server');
|
||||
|
||||
queue.assert('judge');
|
||||
|
||||
async function next(body) {
|
||||
let rdoc = await record.get(body.rid);
|
||||
let $set = {};
|
||||
const $set = {};
|
||||
if (body.case) {
|
||||
rdoc.cases.push(body.case);
|
||||
$set.cases = rdoc.cases;
|
||||
@@ -32,7 +33,7 @@ async function next(body) {
|
||||
}
|
||||
async function end(body) {
|
||||
let rdoc = await record.get(body.rid);
|
||||
let $set = {};
|
||||
const $set = {};
|
||||
if (body.case) {
|
||||
rdoc.cases.push(body.case);
|
||||
$set.cases = rdoc.cases;
|
||||
@@ -59,29 +60,33 @@ class JudgeHandler extends Handler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_JUDGE);
|
||||
}
|
||||
|
||||
async get({ check = false }) {
|
||||
this.response.body = {};
|
||||
if (check) return;
|
||||
let rid = await queue.get('judge', false);
|
||||
const rid = await queue.get('judge', false);
|
||||
if (rid) {
|
||||
let rdoc = await record.get(rid);
|
||||
let pdoc = await problem.getById(rdoc.pid);
|
||||
let task = {
|
||||
const rdoc = await record.get(rid);
|
||||
const pdoc = await problem.getById(rdoc.pid);
|
||||
const task = {
|
||||
event: 'judge',
|
||||
rid, type: 0,
|
||||
rid,
|
||||
type: 0,
|
||||
pid: rdoc.pid,
|
||||
data: pdoc.data,
|
||||
lang: rdoc.lang,
|
||||
code: rdoc.code
|
||||
code: rdoc.code,
|
||||
};
|
||||
this.response.body = { task };
|
||||
}
|
||||
}
|
||||
async post_next() {
|
||||
|
||||
async postNext() {
|
||||
await next(this.request.body);
|
||||
this.response.body = {};
|
||||
}
|
||||
async post_end() {
|
||||
|
||||
async postEnd() {
|
||||
await end(this.request.body);
|
||||
this.response.body = {};
|
||||
}
|
||||
@@ -90,26 +95,29 @@ class JudgeConnectionHandler extends ConnectionHandler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_JUDGE);
|
||||
}
|
||||
|
||||
async message(msg) {
|
||||
if (msg.key == 'next') await next(msg);
|
||||
else if (msg.key == 'end') {
|
||||
if (msg.key === 'next') await next(msg);
|
||||
else if (msg.key === 'end') {
|
||||
await end({ judger: this.user._id, ...msg });
|
||||
this.processing = null;
|
||||
let rid = await queue.get('judge');
|
||||
let rdoc = await record.get(rid);
|
||||
let pdoc = await problem.getById(rdoc.pid);
|
||||
let task = {
|
||||
const rid = await queue.get('judge');
|
||||
const rdoc = await record.get(rid);
|
||||
const pdoc = await problem.getById(rdoc.pid);
|
||||
const task = {
|
||||
event: 'judge',
|
||||
rid, type: 0,
|
||||
rid,
|
||||
type: 0,
|
||||
pid: rdoc.pid,
|
||||
data: pdoc.data,
|
||||
lang: rdoc.lang,
|
||||
code: rdoc.code
|
||||
code: rdoc.code,
|
||||
};
|
||||
this.send({ task });
|
||||
this.processing = task.rid;
|
||||
}
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
if (this.processing) {
|
||||
await record.reset(this.processing);
|
||||
|
||||
+121
-89
@@ -1,23 +1,26 @@
|
||||
const
|
||||
fs = require('fs'),
|
||||
paginate = require('../lib/paginate'),
|
||||
validator = require('../lib/validator'),
|
||||
problem = require('../model/problem'),
|
||||
record = require('../model/record'),
|
||||
user = require('../model/user'),
|
||||
solution = require('../model/solution'),
|
||||
system = require('../model/system'),
|
||||
{ Route, Handler } = require('../service/server'),
|
||||
gridfs = require('../service/gridfs'),
|
||||
queue = require('../service/queue'),
|
||||
{ NoProblemError, ProblemDataNotFoundError, BadRequestError,
|
||||
SolutionNotFoundError } = require('../error'),
|
||||
{ constants } = require('../options'),
|
||||
{ PERM_VIEW_PROBLEM, PERM_VIEW_PROBLEM_HIDDEN, PERM_SUBMIT_PROBLEM,
|
||||
PERM_CREATE_PROBLEM, PERM_READ_PROBLEM_DATA, PERM_EDIT_PROBLEM,
|
||||
PERM_JUDGE, PERM_VIEW_PROBLEM_SOLUTION, PERM_CREATE_PROBLEM_SOLUTION,
|
||||
PERM_EDIT_PROBLEM_SOLUTION, PERM_DELETE_PROBLEM_SOLUTION, PERM_EDIT_PROBLEM_SOLUTION_REPLY,
|
||||
PERM_REPLY_PROBLEM_SOLUTION } = require('../permission');
|
||||
const fs = require('fs');
|
||||
const paginate = require('../lib/paginate');
|
||||
const validator = require('../lib/validator');
|
||||
const problem = require('../model/problem');
|
||||
const record = require('../model/record');
|
||||
const user = require('../model/user');
|
||||
const solution = require('../model/solution');
|
||||
const system = require('../model/system');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const gridfs = require('../service/gridfs');
|
||||
const queue = require('../service/queue');
|
||||
const {
|
||||
NoProblemError, ProblemDataNotFoundError, BadRequestError,
|
||||
SolutionNotFoundError,
|
||||
} = require('../error');
|
||||
const { constants } = require('../options');
|
||||
const {
|
||||
PERM_VIEW_PROBLEM, PERM_VIEW_PROBLEM_HIDDEN, PERM_SUBMIT_PROBLEM,
|
||||
PERM_CREATE_PROBLEM, PERM_READ_PROBLEM_DATA, PERM_EDIT_PROBLEM,
|
||||
PERM_JUDGE, PERM_VIEW_PROBLEM_SOLUTION, PERM_CREATE_PROBLEM_SOLUTION,
|
||||
PERM_EDIT_PROBLEM_SOLUTION, PERM_DELETE_PROBLEM_SOLUTION, PERM_EDIT_PROBLEM_SOLUTION_REPLY,
|
||||
PERM_REPLY_PROBLEM_SOLUTION,
|
||||
} = require('../permission');
|
||||
|
||||
queue.assert('judge');
|
||||
|
||||
@@ -25,27 +28,30 @@ class ProblemHandler extends Handler {
|
||||
async _prepare() {
|
||||
this.checkPerm(PERM_VIEW_PROBLEM);
|
||||
}
|
||||
|
||||
async get({ page = 1, category = null }) {
|
||||
this.response.template = 'problem_main.html';
|
||||
let q = {};
|
||||
const q = {};
|
||||
if (category) q.category = category;
|
||||
if (!this.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pdocs = await problem.getMany(q, { pid: 1 }, page, constants.PROBLEM_PER_PAGE);
|
||||
const pdocs = await problem.getMany(q, { pid: 1 }, page, constants.PROBLEM_PER_PAGE);
|
||||
this.response.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', null]
|
||||
['problem_main', null],
|
||||
],
|
||||
page, pdocs, category: ''
|
||||
page,
|
||||
pdocs,
|
||||
category: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemRandomHandler extends ProblemHandler {
|
||||
async get() {
|
||||
let q = {};
|
||||
const q = {};
|
||||
if (!this.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pid = await problem.random(q);
|
||||
const pid = await problem.random(q);
|
||||
if (!pid) throw new NoProblemError();
|
||||
this.response.body = { pid };
|
||||
}
|
||||
@@ -57,19 +63,20 @@ class ProblemDetailHandler extends ProblemHandler {
|
||||
this.uid = this.user._id;
|
||||
this.pid = pid;
|
||||
if (pid) this.pdoc = await problem.get(pid, this.uid);
|
||||
if (this.pdoc.hidden && this.pdoc.owner != this.uid) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
if (this.pdoc.hidden && this.pdoc.owner !== this.uid) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
if (this.pdoc) this.udoc = await user.getById(this.pdoc.owner);
|
||||
this.response.body = {
|
||||
pdoc: this.pdoc,
|
||||
udoc: this.udoc,
|
||||
title: (this.pdoc || {}).title || ''
|
||||
title: (this.pdoc || {}).title || '',
|
||||
};
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.body.path = [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, null, true]
|
||||
[this.pdoc.title, null, true],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -78,26 +85,31 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_SUBMIT_PROBLEM);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'problem_submit.html';
|
||||
let rdocs = await record.getUserInProblemMulti(this.uid, this.pdoc._id).sort({ _id: -1 }).limit(10).toArray();
|
||||
const rdocs = await record.getUserInProblemMulti(this.uid, this.pdoc._id)
|
||||
.sort({ _id: -1 })
|
||||
.limit(10)
|
||||
.toArray();
|
||||
this.response.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, `/p/${this.pid}`, true],
|
||||
['problem_submit', null]
|
||||
['problem_submit', null],
|
||||
],
|
||||
pdoc: this.pdoc,
|
||||
udoc: this.udoc,
|
||||
rdocs,
|
||||
title: this.pdoc.title
|
||||
title: this.pdoc.title,
|
||||
};
|
||||
}
|
||||
|
||||
async post() {
|
||||
let { lang, code } = this.request.body;
|
||||
let rid = await record.add({
|
||||
uid: this.uid, lang, code, pid: this.pdoc._id
|
||||
const { lang, code } = this.request.body;
|
||||
const rid = await record.add({
|
||||
uid: this.uid, lang, code, pid: this.pdoc._id,
|
||||
});
|
||||
await queue.push('judge', rid);
|
||||
this.user.nSubmit++;
|
||||
@@ -108,7 +120,7 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
|
||||
class ProblemManageHandler extends ProblemDetailHandler {
|
||||
async prepare() {
|
||||
if (this.pdoc.owner != this.uid) this.checkPerm(PERM_EDIT_PROBLEM);
|
||||
if (this.pdoc.owner !== this.uid) this.checkPerm(PERM_EDIT_PROBLEM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +131,10 @@ class ProblemSettingsHandler extends ProblemManageHandler {
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, `/p/${this.pid}`, true],
|
||||
['problem_settings', null]
|
||||
['problem_settings', null],
|
||||
];
|
||||
}
|
||||
|
||||
async post() {
|
||||
// TODO(masnn)
|
||||
this.back();
|
||||
@@ -135,43 +148,45 @@ class ProblemEditHandler extends ProblemManageHandler {
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, `/p/${this.request.params.pid}`, true],
|
||||
['problem_edit', null]
|
||||
['problem_edit', null],
|
||||
];
|
||||
this.response.body.page_name = 'problem_edit';
|
||||
}
|
||||
|
||||
async post({ title, content }) {
|
||||
let pid = validator.checkPid(this.request.body.pid);
|
||||
let pdoc = await problem.get(this.params.pid);
|
||||
const pid = validator.checkPid(this.request.body.pid);
|
||||
const pdoc = await problem.get(this.params.pid);
|
||||
await problem.edit(pdoc._id, { title, content, pid });
|
||||
this.response.redirect = `/p/${pid}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemDataUploadHandler extends ProblemManageHandler {
|
||||
async prepare(){
|
||||
async prepare() {
|
||||
this.response.template = 'problem_upload.html';
|
||||
}
|
||||
|
||||
async get() {
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') {
|
||||
const files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
this.md5 = files[0].md5;
|
||||
}
|
||||
this.response.body.md5 = this.md5;
|
||||
}
|
||||
|
||||
async post() {
|
||||
if (!this.request.files.file) throw new BadRequestError();
|
||||
const r = fs.createReadStream(this.request.files.file.path);
|
||||
let f = gridfs.openUploadStream('data.zip');
|
||||
const f = gridfs.openUploadStream('data.zip');
|
||||
await new Promise((resolve, reject) => {
|
||||
r.pipe(f);
|
||||
f.once('finish', resolve);
|
||||
f.once('error', reject);
|
||||
});
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object')
|
||||
gridfs.delete(this.pdoc.data);
|
||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') { gridfs.delete(this.pdoc.data); }
|
||||
this.pdoc = await problem.edit(this.pdoc._id, { data: f.id });
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') {
|
||||
const files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
this.md5 = files[0].md5;
|
||||
}
|
||||
this.response.body.md5 = this.md5;
|
||||
@@ -180,9 +195,9 @@ class ProblemDataUploadHandler extends ProblemManageHandler {
|
||||
|
||||
class ProblemDataDownloadHandler extends ProblemDetailHandler {
|
||||
async get({ pid }) {
|
||||
if (this.uid != this.pdoc.owner) this.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
||||
if (this.uid !== this.pdoc.owner) this.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
||||
if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid);
|
||||
else if (typeof this.pdoc.data == 'string') this.ctx.setRedirect = this.pdoc.data.split('from:')[1];
|
||||
else if (typeof this.pdoc.data === 'string') [, this.ctx.setRedirect] = this.pdoc.data.split('from:');
|
||||
this.response.attachment(`${this.pdoc.title}.zip`);
|
||||
this.response.body = gridfs.openDownloadStream(this.pdoc.data);
|
||||
}
|
||||
@@ -192,70 +207,81 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
async get({ page = 1 }) {
|
||||
this.response.template = 'problem_solution.html';
|
||||
this.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
let [psdocs, pcount, pscount] = await paginate(solution.getMulti(this.pdoc._id), page, constants.SOLUTION_PER_PAGE);
|
||||
let uids = [this.pdoc.owner], docids = [];
|
||||
for (let psdoc of psdocs) {
|
||||
const [psdocs, pcount, pscount] = await paginate(
|
||||
solution.getMulti(this.pdoc._id),
|
||||
page, constants.SOLUTION_PER_PAGE,
|
||||
);
|
||||
const uids = [this.pdoc.owner]; const
|
||||
docids = [];
|
||||
for (const psdoc of psdocs) {
|
||||
docids.push(psdoc._id);
|
||||
uids.push(psdoc.owner);
|
||||
if (psdoc.reply.length)
|
||||
for (let psrdoc of psdoc.reply)
|
||||
uids.push(psrdoc.owner);
|
||||
if (psdoc.reply.length) { for (const psrdoc of psdoc.reply) uids.push(psrdoc.owner); }
|
||||
}
|
||||
let udict = await user.getList(uids);
|
||||
let pssdict = solution.getListStatus(docids, this.uid);
|
||||
let path = [
|
||||
const udict = await user.getList(uids);
|
||||
const pssdict = solution.getListStatus(docids, this.uid);
|
||||
const path = [
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, `/p/${this.pdoc.pid}`, true],
|
||||
['problem_solution', null]
|
||||
['problem_solution', null],
|
||||
];
|
||||
this.response.body = {
|
||||
path, psdocs, page, pcount, pscount, udict, pssdict
|
||||
path, psdocs, page, pcount, pscount, udict, pssdict,
|
||||
};
|
||||
}
|
||||
|
||||
async post({ psid }) {
|
||||
if (psid) this.psdoc = await solution.get(psid);
|
||||
}
|
||||
async post_submit({ content }) {
|
||||
|
||||
async postSubmit({ content }) {
|
||||
this.checkPerm(PERM_CREATE_PROBLEM_SOLUTION);
|
||||
await solution.add(this.pdoc._id, this.uid, content);
|
||||
this.back();
|
||||
}
|
||||
async post_edit_solution({ content }) {
|
||||
if (this.psdoc.owner != this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION);
|
||||
|
||||
async postEditSolution({ content }) {
|
||||
if (this.psdoc.owner !== this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION);
|
||||
this.psdoc = await solution.edit(this.psdoc._id, content);
|
||||
this.ctx.body.psdoc = this.psdoc;
|
||||
this.back();
|
||||
}
|
||||
async post_delete_solution() {
|
||||
if (this.psdoc.owner != this.uid) this.checkPerm(PERM_DELETE_PROBLEM_SOLUTION);
|
||||
|
||||
async postDeleteSolution() {
|
||||
if (this.psdoc.owner !== this.uid) this.checkPerm(PERM_DELETE_PROBLEM_SOLUTION);
|
||||
await solution.del(this.psdoc._id);
|
||||
this.back();
|
||||
}
|
||||
async post_reply({ psid, content }) {
|
||||
|
||||
async postReply({ psid, content }) {
|
||||
this.checkPerm(PERM_REPLY_PROBLEM_SOLUTION);
|
||||
let psdoc = await solution.get(psid);
|
||||
const psdoc = await solution.get(psid);
|
||||
await solution.reply(psdoc._id, this.uid, content);
|
||||
}
|
||||
async post_edit_reply({ content, psid, psrid }) {
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner != this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
|
||||
async postEditReply({ content, psid, psrid }) {
|
||||
const [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner !== this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
await solution.editReply(psid, psrid, content);
|
||||
}
|
||||
async post_delete_reply({ psid, psrid }) {
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner != this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
|
||||
async postDeleteReply({ psid, psrid }) {
|
||||
const [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner !== this.uid) this.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
await solution.delReply(psid, psrid);
|
||||
this.back();
|
||||
}
|
||||
async post_upvote() {
|
||||
let [psdoc, pssdoc] = await solution.vote(this.psdoc._id, this.uid, 1);
|
||||
|
||||
async postUpvote() {
|
||||
const [psdoc, pssdoc] = await solution.vote(this.psdoc._id, this.uid, 1);
|
||||
this.response.body = { vote: psdoc.vote, user_vote: pssdoc.vote };
|
||||
if (!this.preferJson) this.back();
|
||||
}
|
||||
async post_downvote() {
|
||||
let [psdoc, pssdoc] = await solution.vote(this.psdoc._id, this.uid, -1);
|
||||
|
||||
async postDownvote() {
|
||||
const [psdoc, pssdoc] = await solution.vote(this.psdoc._id, this.uid, -1);
|
||||
this.response.body = { vote: psdoc.vote, user_vote: pssdoc.vote };
|
||||
if (!this.preferJson) this.back();
|
||||
}
|
||||
@@ -264,7 +290,7 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
class ProblemSolutionRawHandler extends ProblemDetailHandler {
|
||||
async get({ psid }) {
|
||||
this.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
let psdoc = await solution.get(psid);
|
||||
const psdoc = await solution.get(psid);
|
||||
this.response.type = 'text/markdown';
|
||||
this.response.body = psdoc.content;
|
||||
}
|
||||
@@ -273,7 +299,7 @@ class ProblemSolutionRawHandler extends ProblemDetailHandler {
|
||||
class ProblemSolutionReplyRawHandler extends ProblemDetailHandler {
|
||||
async get({ psid }) {
|
||||
this.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid);
|
||||
const [psdoc, psrdoc] = await solution.getReply(psid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
this.response.type = 'text/markdown';
|
||||
this.response.body = psrdoc.content;
|
||||
@@ -288,14 +314,20 @@ class ProblemCreateHandler extends Handler {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
['problem_create', null]
|
||||
], page_name: 'problem_create'
|
||||
['problem_create', null],
|
||||
],
|
||||
page_name: 'problem_create',
|
||||
};
|
||||
}
|
||||
async post({ title, pid, content, hidden }) {
|
||||
|
||||
async post({
|
||||
title, pid, content, hidden,
|
||||
}) {
|
||||
validator.checkPid(pid);
|
||||
pid = pid || await system.incPidCounter();
|
||||
await problem.add({ title, content, owner: this.user._id, pid, hidden });
|
||||
await problem.add({
|
||||
title, content, owner: this.user._id, pid, hidden,
|
||||
});
|
||||
this.response.body = { pid };
|
||||
this.response.redirect = `/p/${pid}/settings`;
|
||||
}
|
||||
@@ -322,7 +354,7 @@ class ProblemPretestHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.require_csrfToken
|
||||
@base.sanitize
|
||||
@base.limitRate('add_record', 60, 100)
|
||||
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str,
|
||||
@@ -379,7 +411,7 @@ class ProblemPretestConnection(record_handler.RecordVisibilityMixin, base.Connec
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.require_csrfToken
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, title: str, content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
@@ -408,4 +440,4 @@ class ProblemStatisticsHandler(base.Handler):
|
||||
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
*/
|
||||
*/
|
||||
+57
-34
@@ -1,53 +1,71 @@
|
||||
const
|
||||
{ constants } = require('../options'),
|
||||
{ PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD,
|
||||
PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN } = require('../permission'),
|
||||
problem = require('../model/problem'),
|
||||
record = require('../model/record'),
|
||||
contest = require('../model/contest'),
|
||||
user = require('../model/user'),
|
||||
bus = require('../service/bus'),
|
||||
queue = require('../service/queue'),
|
||||
{ Route, Handler, Connection, ConnectionHandler } = require('../service/server');
|
||||
const { constants } = require('../options');
|
||||
const {
|
||||
PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD,
|
||||
PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN,
|
||||
} = require('../permission');
|
||||
const problem = require('../model/problem');
|
||||
const record = require('../model/record');
|
||||
const contest = require('../model/contest');
|
||||
const user = require('../model/user');
|
||||
const bus = require('../service/bus');
|
||||
const queue = require('../service/queue');
|
||||
const {
|
||||
Route, Handler, Connection, ConnectionHandler,
|
||||
} = require('../service/server');
|
||||
|
||||
class RecordListHandler extends Handler {
|
||||
async get({ page = 1 }) {
|
||||
this.response.template = 'record_main.html';
|
||||
let q = {};
|
||||
let rdocs = await record.getMany(q, { _id: -1 }, page, constants.RECORD_PER_PAGE);
|
||||
let pdict = {}, udict = {};
|
||||
for (let rdoc of rdocs) {
|
||||
udict[rdoc.uid] = await user.getById(rdoc.uid);
|
||||
pdict[rdoc.pid] = await problem.get(rdoc.pid, this.user._id);
|
||||
const q = {};
|
||||
const rdocs = await record.getMany(q, { _id: -1 }, page, constants.RECORD_PER_PAGE);
|
||||
const pdict = {};
|
||||
const udict = {};
|
||||
const ulist = [];
|
||||
const plist = [];
|
||||
for (const rdoc of rdocs) {
|
||||
ulist[rdoc.uid] = user.getById(rdoc.uid);
|
||||
plist[rdoc.pid] = problem.get(rdoc.pid, this.user._id);
|
||||
}
|
||||
await Promise.all(ulist);
|
||||
await Promise.all(plist);
|
||||
for (const udoc of ulist) {
|
||||
udict[udoc._id] = udoc;
|
||||
}
|
||||
for (const pdoc of plist) {
|
||||
pdict[pdoc._id] = pdoc;
|
||||
}
|
||||
this.response.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['record_main', null]
|
||||
['record_main', null],
|
||||
],
|
||||
page, rdocs, pdict, udict
|
||||
page,
|
||||
rdocs,
|
||||
pdict,
|
||||
udict,
|
||||
};
|
||||
}
|
||||
}
|
||||
class RecordDetailHandler extends Handler {
|
||||
async get({ rid }) {
|
||||
this.response.template = 'record_detail.html';
|
||||
let rdoc = await record.get(rid);
|
||||
const rdoc = await record.get(rid);
|
||||
if (rdoc.hidden) this.checkPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
if (rdoc.uid != this.user.uid && !this.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null;
|
||||
if (rdoc.uid !== this.user.uid && !this.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null;
|
||||
this.response.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['record_detail', null]
|
||||
['record_detail', null],
|
||||
],
|
||||
rdoc, show_status: true
|
||||
rdoc,
|
||||
show_status: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
class RecordRejudgeHandler extends Handler {
|
||||
async post({ rid }) {
|
||||
this.checkPerm(PERM_REJUDGE);
|
||||
let rdoc = await record.get(rid);
|
||||
const rdoc = await record.get(rid);
|
||||
if (rdoc) {
|
||||
await record.reset(rid);
|
||||
await queue.push('judge', rid);
|
||||
@@ -59,18 +77,21 @@ class RecordConnectionHandler extends ConnectionHandler {
|
||||
async prepare() {
|
||||
bus.subscribe(['record_change'], this.onRecordChange);
|
||||
}
|
||||
|
||||
async message(msg) {
|
||||
for (let rid of msg.rids) {
|
||||
let rdoc = await record.get(rid);
|
||||
await this.onRecordChange({ value: rdoc });
|
||||
for (const rid of msg.rids) {
|
||||
const rdoc = await record.get(rid); // eslint-disable-line no-await-in-loop
|
||||
await this.onRecordChange({ value: rdoc }); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
bus.unsubscribe(['record_change'], this.onRecordChange);
|
||||
}
|
||||
|
||||
async onRecordChange(data) {
|
||||
let rdoc = data.value;
|
||||
if (rdoc.tid && rdoc.tid.toString() != this.tid) return;
|
||||
const rdoc = data.value;
|
||||
if (rdoc.tid && rdoc.tid.toString() !== this.tid) return;
|
||||
let [udoc, pdoc] = await Promise.all([user.getById(rdoc.uid), problem.getById(rdoc.pid)]);
|
||||
if (pdoc.hidden && !this.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) pdoc = null;
|
||||
this.send({ html: await this.renderHTML('record_main_tr.html', { rdoc, udoc, pdoc }) });
|
||||
@@ -78,9 +99,9 @@ class RecordConnectionHandler extends ConnectionHandler {
|
||||
}
|
||||
class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) {
|
||||
async prepare({ rid }) {
|
||||
let rdoc = await record.get(rid);
|
||||
const rdoc = await record.get(rid);
|
||||
if (rdoc.tid) {
|
||||
let tdoc = await contest.get(rdoc.tid);
|
||||
const tdoc = await contest.get(rdoc.tid);
|
||||
if (!this.canShowRecord(tdoc)) {
|
||||
this.close();
|
||||
return;
|
||||
@@ -90,14 +111,16 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
|
||||
bus.subscribe(['record_change'], this.onRecordChange);
|
||||
this.onRecordChange({ value: rdoc });
|
||||
}
|
||||
|
||||
async onRecordChange(data) {
|
||||
let rdoc = data.value;
|
||||
if (rdoc._id.toString() != this.rid) return;
|
||||
const rdoc = data.value;
|
||||
if (rdoc._id.toString() !== this.rid) return;
|
||||
this.send({
|
||||
status_html: await this.renderHTML('record_detail_status.html', { rdoc }),
|
||||
summary_html: await this.renderHTML('record_detail_summary.html', { rdoc })
|
||||
summary_html: await this.renderHTML('record_detail_summary.html', { rdoc }),
|
||||
});
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
bus.unsubscribe(['record_change'], this.onRecordChange);
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
const
|
||||
opcount = require('../model/opcount'),
|
||||
{ PermissionError } = require('../error');
|
||||
|
||||
module.exports = {
|
||||
requirePerm(perm) {
|
||||
return async (ctx, next) => {
|
||||
for (let i in arguments) {
|
||||
if (arguments[i] instanceof Array) {
|
||||
let p = false;
|
||||
for (let j in arguments)
|
||||
if (ctx.state.user.hasPerm(arguments[i][j])) {
|
||||
p = true;
|
||||
break;
|
||||
}
|
||||
if (!p) throw new PermissionError([arguments[i]]);
|
||||
} else {
|
||||
if (ctx.state.user.hasPerm(arguments[i])) continue;
|
||||
else throw new PermissionError([arguments[i]]);
|
||||
}
|
||||
}
|
||||
await next();
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
+61
-39
@@ -1,22 +1,24 @@
|
||||
const
|
||||
{ Route, Handler } = require('../service/server.js'),
|
||||
user = require('../model/user'),
|
||||
token = require('../model/token'),
|
||||
system = require('../model/system'),
|
||||
{ sendMail } = require('../lib/mail'),
|
||||
misc = require('../lib/misc'),
|
||||
validator = require('../lib/validator'),
|
||||
options = require('../options'),
|
||||
{ PERM_REGISTER_USER, PERM_LOGGEDIN } = require('../permission'),
|
||||
{ UserAlreadyExistError, InvalidTokenError, VerifyPasswordError,
|
||||
UserNotFoundError, LoginError, SystemError } = require('../error');
|
||||
const { Route, Handler } = require('../service/server.js');
|
||||
const user = require('../model/user');
|
||||
const token = require('../model/token');
|
||||
const system = require('../model/system');
|
||||
const { sendMail } = require('../lib/mail');
|
||||
const misc = require('../lib/misc');
|
||||
const validator = require('../lib/validator');
|
||||
const options = require('../options');
|
||||
const { PERM_REGISTER_USER, PERM_LOGGEDIN } = require('../permission');
|
||||
const {
|
||||
UserAlreadyExistError, InvalidTokenError, VerifyPasswordError,
|
||||
UserNotFoundError, LoginError, SystemError,
|
||||
} = require('../error');
|
||||
|
||||
class UserLoginHandler extends Handler {
|
||||
async get() {
|
||||
this.response.template = 'user_login.html';
|
||||
}
|
||||
|
||||
async post({ uname, password, rememberme = false }) {
|
||||
let udoc = await user.getByUname(uname);
|
||||
const udoc = await user.getByUname(uname);
|
||||
if (!udoc) throw new LoginError(uname);
|
||||
if (udoc) udoc.checkPassword(password);
|
||||
await user.setById(udoc._id, { loginat: new Date(), loginip: this.request.ip });
|
||||
@@ -25,7 +27,7 @@ class UserLoginHandler extends Handler {
|
||||
this.session.uid = udoc._id;
|
||||
this.session.rememberme = rememberme;
|
||||
this.response.body = {};
|
||||
let referer = this.request.headers.referer || '/';
|
||||
const referer = this.request.headers.referer || '/';
|
||||
this.response.redirect = referer.endsWith('/login') ? '/' : referer;
|
||||
}
|
||||
}
|
||||
@@ -34,9 +36,11 @@ class UserLogoutHandler extends Handler {
|
||||
super(ctx);
|
||||
this.checkPerm(PERM_LOGGEDIN);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'user_logout.html';
|
||||
}
|
||||
|
||||
async post() {
|
||||
this.session = { uid: 1 };
|
||||
this.response.body = {};
|
||||
@@ -47,16 +51,22 @@ class UserRegisterHandler extends Handler {
|
||||
super(ctx);
|
||||
this.checkPerm(PERM_REGISTER_USER);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'user_register.html';
|
||||
}
|
||||
|
||||
async post({ mail }) {
|
||||
validator.checkEmail(mail);
|
||||
if (await user.getByEmail(mail, true)) throw new UserAlreadyExistError(mail);
|
||||
this.limitRate('send_mail', 3600, 30);
|
||||
let t = await token.add(token.TYPE_REGISTRATION, options.registration_token_expire_seconds, { mail });
|
||||
const t = await token.add(
|
||||
token.TYPE_REGISTRATION,
|
||||
options.registration_token_expire_seconds,
|
||||
{ mail },
|
||||
);
|
||||
if (options.smtp.user) {
|
||||
let m = await this.renderHTML('user_register_mail', { url: `/register/${t}` });
|
||||
const m = await this.renderHTML('user_register_mail', { url: `/register/${t}` });
|
||||
await sendMail(mail, 'Sign Up', 'user_register_mail', m);
|
||||
this.response.body = {};
|
||||
this.response.template = 'user_register_mail_sent.html';
|
||||
@@ -70,18 +80,24 @@ class UserRegisterWithCodeHandler extends Handler {
|
||||
super(ctx);
|
||||
this.checkPerm(PERM_REGISTER_USER);
|
||||
}
|
||||
|
||||
async get({ code }) {
|
||||
this.response.template = 'user_register_with_code.html';
|
||||
let { mail } = await token.get(code, token.TYPE_REGISTRATION);
|
||||
const { mail } = await token.get(code, token.TYPE_REGISTRATION);
|
||||
if (!mail) throw new InvalidTokenError(token.TYPE_REGISTRATION, code);
|
||||
this.response.body = { mail };
|
||||
}
|
||||
async post({ code, password, verify_password, uname }) {
|
||||
let { mail } = await token.get(code, token.TYPE_REGISTRATION);
|
||||
|
||||
async post({
|
||||
code, password, verifyPassword, uname,
|
||||
}) {
|
||||
const { mail } = await token.get(code, token.TYPE_REGISTRATION);
|
||||
if (!mail) throw new InvalidTokenError(token.TYPE_REGISTRATION, code);
|
||||
if (password != verify_password) throw new VerifyPasswordError();
|
||||
let uid = await system.incUserCounter();
|
||||
await user.create({ uid, uname, password, mail, regip: this.request.ip });
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
const uid = await system.incUserCounter();
|
||||
await user.create({
|
||||
uid, uname, password, mail, regip: this.request.ip,
|
||||
});
|
||||
await token.delete(code, token.TYPE_REGISTRATION);
|
||||
this.session.uid = uid;
|
||||
this.response.body = {};
|
||||
@@ -93,19 +109,21 @@ class UserLostPassHandler extends Handler {
|
||||
if (!options.smtp.user) throw new SystemError('Cannot send mail');
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
async get() {
|
||||
this.response.template = 'user_lostpass.html';
|
||||
}
|
||||
|
||||
async post({ mail }) {
|
||||
validator.checkEmail(mail);
|
||||
let udoc = await user.getByEmail(mail);
|
||||
const udoc = await user.getByEmail(mail);
|
||||
if (!udoc) throw new UserNotFoundError(mail);
|
||||
let tid = await token.add(
|
||||
const tid = await token.add(
|
||||
token.TYPE_LOSTPASS,
|
||||
options.lostpass_token_expire_seconds,
|
||||
{ uid: udoc._id }
|
||||
{ uid: udoc._id },
|
||||
);
|
||||
let m = await this.renderHTML('user_lostpass_mail', { url: `/lostpass/${tid}`, uname: udoc.uname });
|
||||
const m = await this.renderHTML('user_lostpass_mail', { url: `/lostpass/${tid}`, uname: udoc.uname });
|
||||
await sendMail(mail, 'Lost Password', 'user_lostpass_mail', m);
|
||||
this.response.body = {};
|
||||
this.response.template = 'user_lostpass_mail_sent.html';
|
||||
@@ -113,15 +131,16 @@ class UserLostPassHandler extends Handler {
|
||||
}
|
||||
class UserLostPassWithCodeHandler extends Handler {
|
||||
async get({ code }) {
|
||||
let tdoc = await token.get(code, token.TYPE_LOSTPASS);
|
||||
const tdoc = await token.get(code, token.TYPE_LOSTPASS);
|
||||
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
|
||||
let udoc = await user.getById(tdoc.uid);
|
||||
const udoc = await user.getById(tdoc.uid);
|
||||
this.response.body = { uname: udoc.uname };
|
||||
}
|
||||
async post({ code, password, verify_password }) {
|
||||
let tdoc = await token.get(code, token.TYPE_LOSTPASS);
|
||||
|
||||
async post({ code, password, verifyPassword }) {
|
||||
const tdoc = await token.get(code, token.TYPE_LOSTPASS);
|
||||
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
|
||||
if (password != verify_password) throw new VerifyPasswordError();
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
await user.setPassword(tdoc.uid, password);
|
||||
await token.delete(code, token.TYPE_LOSTPASS);
|
||||
this.response.redirect = '/';
|
||||
@@ -132,28 +151,31 @@ class UserDetailHandler extends Handler {
|
||||
super(ctx);
|
||||
this.response.template = 'user_detail.html';
|
||||
}
|
||||
|
||||
async get({ uid }) {
|
||||
let isSelfProfile = this.ctx.state.user._id == uid;
|
||||
let udoc = await user.getById(uid);
|
||||
const isSelfProfile = this.ctx.state.user._id === uid;
|
||||
const udoc = await user.getById(uid);
|
||||
if (!udoc) throw new UserNotFoundError(uid);
|
||||
let sdoc = await token.getMostRecentSessionByUid(uid);
|
||||
const sdoc = await token.getMostRecentSessionByUid(uid);
|
||||
this.ctx.body = { isSelfProfile, udoc, sdoc };
|
||||
}
|
||||
}
|
||||
class UserSearchHandler extends Handler {
|
||||
async get({ q, exact_match = false }) {
|
||||
async get({ q, exactMatch = false }) {
|
||||
let udocs;
|
||||
if (exact_match) udocs = [];
|
||||
if (exactMatch) udocs = [];
|
||||
else udocs = await user.getPrefixList(q, 20);
|
||||
try {
|
||||
let udoc = await user.getById(parseInt(q));
|
||||
const udoc = await user.getById(parseInt(q));
|
||||
if (udoc) udocs.insert(0, udoc);
|
||||
} catch (e) {
|
||||
/* Ignore */
|
||||
}
|
||||
for (let i in udocs)
|
||||
if (udocs[i].gravatar)
|
||||
for (const i in udocs) {
|
||||
if (udocs[i].gravatar) {
|
||||
udocs[i].gravatar_url = misc.gravatar_url[udocs[i].gravatar];
|
||||
}
|
||||
}
|
||||
this.ctx.body = { udocs };
|
||||
}
|
||||
}
|
||||
|
||||
+27
-28
@@ -1,32 +1,31 @@
|
||||
require('./utils');
|
||||
const
|
||||
Mongo = require('mongodb'),
|
||||
{ defaults } = require('lodash'),
|
||||
builtin = require('./model/builtin'),
|
||||
pwhash = require('./lib/pwhash'),
|
||||
options = require('./options'),
|
||||
{ udoc } = require('./interface');
|
||||
const Mongo = require('mongodb');
|
||||
const { defaults } = require('lodash');
|
||||
const builtin = require('./model/builtin');
|
||||
const pwhash = require('./lib/pwhash');
|
||||
const options = require('./options');
|
||||
const { udoc } = require('./interface');
|
||||
|
||||
async function run() {
|
||||
let mongourl = 'mongodb://';
|
||||
if (options.db.username) mongourl += options.db.username + ':' + options.db.password + '@';
|
||||
if (options.db.username) mongourl += `${options.db.username}:${options.db.password}@`;
|
||||
mongourl += `${options.db.host}:${options.db.port}/${options.db.name}`;
|
||||
let Database = await Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
let db = Database.db(options.db.name);
|
||||
let coll_user = db.collection('user');
|
||||
let coll_role = db.collection('role');
|
||||
let coll_blacklist = db.collection('blacklist');
|
||||
let coll_token = db.collection('token');
|
||||
const Database = await Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
const db = Database.db(options.db.name);
|
||||
const collUser = db.collection('user');
|
||||
const collRole = db.collection('role');
|
||||
const collBlacklist = db.collection('blacklist');
|
||||
const collToken = db.collection('token');
|
||||
async function createUser() {
|
||||
let salt = pwhash.salt();
|
||||
await coll_user.insertMany([
|
||||
const salt = pwhash.salt();
|
||||
await collUser.insertMany([
|
||||
defaults({
|
||||
_id: 0,
|
||||
uname: 'Hydro',
|
||||
unameLower: 'hydro',
|
||||
mail: 'hydro@hydro',
|
||||
mailLower: 'hydro@hydro',
|
||||
role: 'guest'
|
||||
role: 'guest',
|
||||
}, udoc),
|
||||
defaults({
|
||||
_id: 1,
|
||||
@@ -34,7 +33,7 @@ async function run() {
|
||||
mailLower: 'guest@hydro',
|
||||
uname: 'Guest',
|
||||
unameLower: 'guest',
|
||||
role: 'guest'
|
||||
role: 'guest',
|
||||
}, udoc),
|
||||
defaults({
|
||||
_id: -1,
|
||||
@@ -45,22 +44,22 @@ async function run() {
|
||||
hash: pwhash.hash('rootroot', salt),
|
||||
salt,
|
||||
gravatar: 'root@hydro',
|
||||
role: 'admin'
|
||||
}, udoc)
|
||||
role: 'admin',
|
||||
}, udoc),
|
||||
]);
|
||||
}
|
||||
await coll_user.createIndex('unameLower', { unique: true });
|
||||
await coll_user.createIndex('mailLower', { sparse: true });
|
||||
await coll_role.insertMany(builtin.BUILTIN_ROLES);
|
||||
await coll_blacklist.createIndex('expireAt', { expireAfterSeconds: 0 });
|
||||
await coll_token.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true });
|
||||
await coll_token.createIndex('expireAt', { expireAfterSeconds: 0 });
|
||||
await collUser.createIndex('unameLower', { unique: true });
|
||||
await collUser.createIndex('mailLower', { sparse: true });
|
||||
await collRole.insertMany(builtin.BUILTIN_ROLES);
|
||||
await collBlacklist.createIndex('expireAt', { expireAfterSeconds: 0 });
|
||||
await collToken.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true });
|
||||
await collToken.createIndex('expireAt', { expireAfterSeconds: 0 });
|
||||
await createUser();
|
||||
console.log('Installed');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run().catch(e => {
|
||||
run().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -16,7 +16,7 @@ exports.udoc = {
|
||||
regip: '0.0.0.0',
|
||||
gravatar: '',
|
||||
loginat: new Date(),
|
||||
loginip: '0.0.0.0'
|
||||
loginip: '0.0.0.0',
|
||||
};
|
||||
/*
|
||||
export interface Pdoc {
|
||||
@@ -68,4 +68,4 @@ export interface Tdoc {
|
||||
content: string
|
||||
pids: ObjectID[]
|
||||
}
|
||||
*/
|
||||
*/
|
||||
+24
-21
@@ -1,37 +1,40 @@
|
||||
const
|
||||
fs = require('fs'),
|
||||
yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
let locales = {};
|
||||
const locales = {};
|
||||
|
||||
String.prototype.format = function (args) {
|
||||
var result = this;
|
||||
if (arguments.length > 0) {
|
||||
if (arguments.length == 1 && typeof (args) == 'object') {
|
||||
for (var key in args)
|
||||
if (args[key] != undefined) {
|
||||
let reg = new RegExp('(\\{' + key + '\\})', 'g');
|
||||
String.prototype.format = function formatStr(...args) {
|
||||
let result = this;
|
||||
if (args.length > 0) {
|
||||
if (args.length === 1 && typeof (args[0]) === 'object') {
|
||||
for (const key in args) {
|
||||
if (args[key] !== undefined) {
|
||||
const reg = new RegExp(`(\\{${key}\\})`, 'g');
|
||||
result = result.replace(reg, args[key]);
|
||||
}
|
||||
} else for (var i = 0; i < arguments.length; i++)
|
||||
if (arguments[i] != undefined) {
|
||||
let reg = new RegExp('(\\{)' + i + '(\\})', 'g');
|
||||
result = result.replace(reg, arguments[i]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] !== undefined) {
|
||||
const reg = new RegExp(`(\\{)${i}(\\})`, 'g');
|
||||
result = result.replace(reg, args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
String.prototype.rawformat = function (object) {
|
||||
let res = this.split('{@}');
|
||||
String.prototype.rawformat = function rawFormat(object) {
|
||||
const res = this.split('{@}');
|
||||
return [res[0], object, res[1]];
|
||||
};
|
||||
String.prototype.translate = function (language = 'zh_CN') {
|
||||
String.prototype.translate = function translate(language = 'zh_CN') {
|
||||
if (locales[language]) return locales[language][this] || this;
|
||||
else return this;
|
||||
return this;
|
||||
};
|
||||
|
||||
module.exports = function load(file, language) {
|
||||
if (!locales[language]) locales[language] = {};
|
||||
let content = fs.readFileSync(file).toString();
|
||||
const content = fs.readFileSync(file).toString();
|
||||
Object.assign(locales[language], yaml.safeLoad(content));
|
||||
};
|
||||
};
|
||||
+13
-11
@@ -1,7 +1,6 @@
|
||||
const
|
||||
nodemailer = require('nodemailer'),
|
||||
options = require('../options'),
|
||||
{ SendMailError } = require('../error');
|
||||
const nodemailer = require('nodemailer');
|
||||
const options = require('../options');
|
||||
const { SendMailError } = require('../error');
|
||||
|
||||
let transporter = null;
|
||||
try {
|
||||
@@ -11,8 +10,8 @@ try {
|
||||
secure: options.VJ_SMTP_SECURE,
|
||||
auth: {
|
||||
user: options.VJ_SMTP_USER,
|
||||
pass: options.VJ_SMTP_PASSWORD
|
||||
}
|
||||
pass: options.VJ_SMTP_PASSWORD,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -20,8 +19,8 @@ try {
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {string} to
|
||||
* @param {string} subject
|
||||
* @param {string} to
|
||||
* @param {string} subject
|
||||
* @param {string} text
|
||||
* @param {string} html
|
||||
*/
|
||||
@@ -31,7 +30,10 @@ module.exports = {
|
||||
t = await new Promise((resolve, reject) => {
|
||||
transporter.sendMail({
|
||||
from: options.VJ_SMTP_FROM,
|
||||
to, subject, text, html
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
}, (err, info) => {
|
||||
if (err) reject(err);
|
||||
else resolve(info);
|
||||
@@ -41,5 +43,5 @@ module.exports = {
|
||||
throw new SendMailError(to);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,19 +1,19 @@
|
||||
const
|
||||
hljs = require('highlight.js'),
|
||||
MarkdownIt = require('markdown-it'),
|
||||
katex = require('markdown-it-katex');
|
||||
const hljs = require('highlight.js');
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const katex = require('markdown-it-katex');
|
||||
|
||||
class Markdown extends MarkdownIt {
|
||||
constructor() {
|
||||
super({
|
||||
linkify: true,
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hljs.getLanguage(lang))
|
||||
highlight(str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return hljs.highlight(lang, str).value;
|
||||
} catch (__) { } // eslint-disable-line no-empty
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
});
|
||||
this.linkify.tlds('.py', false);
|
||||
this.use(katex);
|
||||
|
||||
+27
-31
@@ -1,8 +1,7 @@
|
||||
|
||||
'use strict';
|
||||
/* eslint-disable no-bitwise */
|
||||
function safeAdd(x, y) {
|
||||
var lsw = (x & 0xffff) + (y & 0xffff);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
const lsw = (x & 0xffff) + (y & 0xffff);
|
||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
}
|
||||
const bitRotateLeft = (num, cnt) => (num << cnt) | (num >>> (32 - cnt));
|
||||
@@ -14,15 +13,15 @@ const md5ii = (a, b, c, d, x, s, t) => md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
||||
function binlMD5(x, len) {
|
||||
x[len >> 5] |= 0x80 << len % 32;
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
var i;
|
||||
var olda;
|
||||
var oldb;
|
||||
var oldc;
|
||||
var oldd;
|
||||
var a = 1732584193;
|
||||
var b = -271733879;
|
||||
var c = -1732584194;
|
||||
var d = 271733878;
|
||||
let i;
|
||||
let olda;
|
||||
let oldb;
|
||||
let oldc;
|
||||
let oldd;
|
||||
let a = 1732584193;
|
||||
let b = -271733879;
|
||||
let c = -1732584194;
|
||||
let d = 271733878;
|
||||
for (i = 0; i < x.length; i += 16) {
|
||||
olda = a;
|
||||
oldb = b;
|
||||
@@ -100,34 +99,31 @@ function binlMD5(x, len) {
|
||||
return [a, b, c, d];
|
||||
}
|
||||
function binl2rstr(input) {
|
||||
var i;
|
||||
var output = '';
|
||||
var length32 = input.length * 32;
|
||||
for (i = 0; i < length32; i += 8)
|
||||
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
|
||||
let i;
|
||||
let output = '';
|
||||
const length32 = input.length * 32;
|
||||
for (i = 0; i < length32; i += 8) { output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); }
|
||||
return output;
|
||||
}
|
||||
function rstr2binl(input) {
|
||||
var i;
|
||||
var output = [];
|
||||
let i;
|
||||
const output = [];
|
||||
output[(input.length >> 2) - 1] = undefined;
|
||||
for (i = 0; i < output.length; i += 1)
|
||||
output[i] = 0;
|
||||
var length8 = input.length * 8;
|
||||
for (i = 0; i < length8; i += 8)
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
|
||||
for (i = 0; i < output.length; i += 1) { output[i] = 0; }
|
||||
const length8 = input.length * 8;
|
||||
for (i = 0; i < length8; i += 8) output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
|
||||
return output;
|
||||
}
|
||||
const rstrMD5 = s => binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
|
||||
const rstrMD5 = (s) => binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
|
||||
function rstr2hex(input) {
|
||||
var hexTab = '0123456789abcdef';
|
||||
var output = '';
|
||||
var x;
|
||||
var i;
|
||||
const hexTab = '0123456789abcdef';
|
||||
let output = '';
|
||||
let x;
|
||||
let i;
|
||||
for (i = 0; i < input.length; i += 1) {
|
||||
x = input.charCodeAt(i);
|
||||
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
module.exports = s => rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
|
||||
module.exports = (s) => rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
|
||||
+23
-20
@@ -1,41 +1,44 @@
|
||||
const md5 = require('./md5');
|
||||
exports.gravatar_url = function (email, size) {
|
||||
|
||||
exports.gravatar_url = function gravatarUrl(email, size) {
|
||||
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size}`;
|
||||
};
|
||||
exports.datetime_span = function (dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
|
||||
exports.datetime_span = function datetimeSpan(dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
|
||||
if (dt.generationTime) dt = new Date(dt.generationTime * 1000);
|
||||
else if (typeof dt == 'number' || typeof dt == 'string') dt = new Date(dt);
|
||||
else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt);
|
||||
return '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
|
||||
relative ? ' relative' : '',
|
||||
dt.getTime() / 1000,
|
||||
dt.format(format)
|
||||
dt.format(format),
|
||||
);
|
||||
};
|
||||
exports.paginate = function* (page, num_pages) {
|
||||
let radius = 2, first, last;
|
||||
exports.paginate = function* paginate(page, numPages) {
|
||||
const radius = 2; let first; let
|
||||
last;
|
||||
if (page > 1) {
|
||||
yield ['first', 1];
|
||||
yield ['previous', page - 1];
|
||||
}
|
||||
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, num_pages)];
|
||||
else if (page >= num_pages - radius) [first, last] = [Math.max(1, num_pages - radius * 2), num_pages];
|
||||
else[first, last] = [page - radius, page + radius];
|
||||
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, numPages)];
|
||||
else if (page >= numPages - radius) {
|
||||
[first, last] = [Math.max(1, numPages - radius * 2), numPages];
|
||||
} else [first, last] = [page - radius, page + radius];
|
||||
if (first > 1) yield ['ellipsis', 0];
|
||||
for (let page0 = first; page0 < last + 1; page0++) {
|
||||
if (page0 != page) yield ['page', page0];
|
||||
if (page0 !== page) yield ['page', page0];
|
||||
else yield ['current', page];
|
||||
}
|
||||
if (last < num_pages) yield ['ellipsis', 0];
|
||||
if (page < num_pages) yield ['next', page + 1];
|
||||
yield ['last', num_pages];
|
||||
if (last < numPages) yield ['ellipsis', 0];
|
||||
if (page < numPages) yield ['next', page + 1];
|
||||
yield ['last', numPages];
|
||||
};
|
||||
exports.format_size = function (size, base = 1) {
|
||||
exports.format_size = function formatSize(size, base = 1) {
|
||||
size *= base;
|
||||
let unit = 1024;
|
||||
let unit_names = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
for (let unit_name of unit_names) {
|
||||
if (size < unit) return '{0} {1}'.format(Math.round(size), unit_name);
|
||||
const unit = 1024;
|
||||
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
for (const unitName of unitNames) {
|
||||
if (size < unit) return '{0} {1}'.format(Math.round(size), unitName);
|
||||
size /= unit;
|
||||
}
|
||||
return '{0} {1}'.format(Math.round(size * unit), unit_names[unit_names.length - 1]);
|
||||
};
|
||||
return '{0} {1}'.format(Math.round(size * unit), unitNames[unitNames.length - 1]);
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
const { ValidationError } = require('../error');
|
||||
module.exports = async function paginate(cursor, page, page_size) {
|
||||
|
||||
module.exports = async function paginate(cursor, page, pageSize) {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
let [count, page_docs] = await Promise.all([
|
||||
const [count, pageDocs] = await Promise.all([
|
||||
cursor.count(),
|
||||
cursor.skip((page - 1) * page_size).limit(page_size).toArray()
|
||||
cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(),
|
||||
]);
|
||||
let num_pages = Math.floor((count + page_size - 1) / page_size);
|
||||
return [page_docs, num_pages, count];
|
||||
const numPages = Math.floor((count + pageSize - 1) / pageSize);
|
||||
return [pageDocs, numPages, count];
|
||||
};
|
||||
+9
-8
@@ -1,26 +1,27 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
module.exports = {
|
||||
_b64encode: str => new Buffer(str).toString('base64'),
|
||||
_b64decode: str => new Buffer(str, 'base64').toString(),
|
||||
_md5: str => crypto.createHash('md5').update(str).digest('hex'),
|
||||
_sha1: str => crypto.createHash('sha1').update(str).digest('hex'),
|
||||
_b64encode: (str) => Buffer.from(str).toString('base64'),
|
||||
_b64decode: (str) => Buffer.from(str, 'base64').toString(),
|
||||
_md5: (str) => crypto.createHash('md5').update(str).digest('hex'),
|
||||
_sha1: (str) => crypto.createHash('sha1').update(str).digest('hex'),
|
||||
/**
|
||||
* @param {string} password
|
||||
* @param {string} salt
|
||||
* @param {string} hash
|
||||
*/
|
||||
check(password, salt, hash) {
|
||||
return hash == this.hash(password, salt);
|
||||
return hash === this.hash(password, salt);
|
||||
},
|
||||
/**
|
||||
* @param {string} password
|
||||
* @param {string} salt
|
||||
*/
|
||||
hash(password, salt) {
|
||||
let res = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256');
|
||||
const res = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256');
|
||||
return res.toString('Hex');
|
||||
},
|
||||
salt() {
|
||||
return String.random();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
module.exports = function* ranked(diter, equ = (a, b) => a == b) {
|
||||
let last_doc = null;
|
||||
let r = 0, count = 0;
|
||||
for (let doc of diter) {
|
||||
module.exports = function* ranked(diter, equ = (a, b) => a === b) {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
for (const doc of diter) {
|
||||
count++;
|
||||
if (count == 1 || !equ(last_doc, doc))
|
||||
r = count;
|
||||
last_doc = doc;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
yield [r, doc];
|
||||
}
|
||||
};
|
||||
+17
-24
@@ -1,34 +1,27 @@
|
||||
const
|
||||
path = require('path'),
|
||||
nunjucks = require('nunjucks'),
|
||||
markdown = require('./markdown'),
|
||||
options = require('../options'),
|
||||
perm = require('../permission'),
|
||||
builtin = require('../model/builtin'),
|
||||
model = require('../model'),
|
||||
misc = require('../lib/misc');
|
||||
const path = require('path');
|
||||
const nunjucks = require('nunjucks');
|
||||
const markdown = require('./markdown');
|
||||
const options = require('../options');
|
||||
const perm = require('../permission');
|
||||
const builtin = require('../model/builtin');
|
||||
const model = require('../model');
|
||||
const misc = require('./misc');
|
||||
|
||||
class Nunjucks extends nunjucks.Environment {
|
||||
constructor() {
|
||||
super(
|
||||
new nunjucks.FileSystemLoader(path.resolve(options.template.path)),
|
||||
{ autoescape: true, trimBlocks: true }
|
||||
{ autoescape: true, trimBlocks: true },
|
||||
);
|
||||
this.addFilter('json', function (self) {
|
||||
return JSON.stringify(self);
|
||||
}, false);
|
||||
this.addFilter('assign', function (self, data) {
|
||||
return Object.assign(self, data);
|
||||
});
|
||||
this.addFilter('markdown', function (self) {
|
||||
return markdown.render(self);
|
||||
});
|
||||
this.addFilter('json', (self) => JSON.stringify(self), false);
|
||||
this.addFilter('assign', (self, data) => Object.assign(self, data));
|
||||
this.addFilter('markdown', (self) => markdown.render(self));
|
||||
this.addFilter('gravatar_url', misc.gravatar_url);
|
||||
this.addFilter('format_size', misc.format_size);
|
||||
this.addGlobal('typeof', o => typeof o);
|
||||
this.addGlobal('typeof', (o) => typeof o);
|
||||
this.addGlobal('console', console);
|
||||
this.addGlobal('static_url', str => `/${str}`);
|
||||
this.addGlobal('reverse_url', str => str);
|
||||
this.addGlobal('static_url', (str) => `/${str}`);
|
||||
this.addGlobal('reverse_url', (str) => str);
|
||||
this.addGlobal('datetime_span', misc.datetime_span);
|
||||
this.addGlobal('paginate', misc.paginate);
|
||||
this.addGlobal('perm', perm);
|
||||
@@ -36,6 +29,6 @@ class Nunjucks extends nunjucks.Environment {
|
||||
this.addGlobal('model', model);
|
||||
}
|
||||
}
|
||||
let env = new Nunjucks();
|
||||
const env = new Nunjucks();
|
||||
|
||||
module.exports = env;
|
||||
module.exports = env;
|
||||
+39
-32
@@ -1,37 +1,44 @@
|
||||
const
|
||||
{ ValidationError } = require('../error'),
|
||||
RE_UID = /^-?\d+$/i,
|
||||
RE_PID = /^([a-zA-Z]+[a-zA-Z0-9]*)|$/i,
|
||||
RE_UNAME = /[^\s\u3000](.{,254}[^\s\u3000])?$/i,
|
||||
RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
||||
const { ValidationError } = require('../error');
|
||||
|
||||
const
|
||||
isTitle = s => s && s.length < 64,
|
||||
checkTitle = s => { if (!isTitle(s)) throw new ValidationError('title'); else return s; },
|
||||
isUid = s => RE_UID.test(s),
|
||||
checkUid = s => { if (!isUid(s)) throw new ValidationError('uid'); else return s; },
|
||||
isUname = s => RE_UNAME.test(s),
|
||||
checkUname = s => { if (!isUname(s)) throw new ValidationError('uname'); else return s; },
|
||||
isPassword = s => s.length >= 5,
|
||||
checkPassword = s => { if (!isPassword(s)) throw new ValidationError('password'); else return s; },
|
||||
isEmail = s => RE_MAIL.test(s),
|
||||
checkEmail = s => { if (!isEmail(s)) throw new ValidationError('mail'); else return s; },
|
||||
isContent = s => s && s.length < 65536,
|
||||
checkContent = s => { if (!isContent(s)) throw new ValidationError('content'); else return s; },
|
||||
isName = s => s && s.length < 256,
|
||||
checkName = s => { if (!isName(s)) throw new ValidationError('name'); else return s; },
|
||||
isPid = s => RE_PID.test(s.toString()),
|
||||
checkPid = s => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
||||
const RE_UID = /^-?\d+$/i;
|
||||
const RE_PID = /^([a-zA-Z]+[a-zA-Z0-9]*)|$/i;
|
||||
const RE_UNAME = /[^\s\u3000](.{,254}[^\s\u3000])?$/i;
|
||||
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
||||
|
||||
const isTitle = (s) => s && s.length < 64;
|
||||
const checkTitle = (s) => { if (!isTitle(s)) throw new ValidationError('title'); else return s; };
|
||||
const isUid = (s) => RE_UID.test(s);
|
||||
const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
|
||||
const isUname = (s) => RE_UNAME.test(s);
|
||||
const checkUname = (s) => { if (!isUname(s)) throw new ValidationError('uname'); else return s; };
|
||||
const isPassword = (s) => s.length >= 5;
|
||||
const checkPassword = (s) => { if (!isPassword(s)) throw new ValidationError('password'); else return s; };
|
||||
const isEmail = (s) => RE_MAIL.test(s);
|
||||
const checkEmail = (s) => { if (!isEmail(s)) throw new ValidationError('mail'); else return s; };
|
||||
const isContent = (s) => s && s.length < 65536;
|
||||
const checkContent = (s) => { if (!isContent(s)) throw new ValidationError('content'); else return s; };
|
||||
const isName = (s) => s && s.length < 256;
|
||||
const checkName = (s) => { if (!isName(s)) throw new ValidationError('name'); else return s; };
|
||||
const isPid = (s) => RE_PID.test(s.toString());
|
||||
const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
||||
|
||||
module.exports = {
|
||||
isTitle, checkTitle,
|
||||
isUid, checkUid,
|
||||
isUname, checkUname,
|
||||
isPassword, checkPassword,
|
||||
isEmail, checkEmail,
|
||||
isContent, checkContent,
|
||||
isName, checkName,
|
||||
isPid, checkPid
|
||||
isTitle,
|
||||
checkTitle,
|
||||
isUid,
|
||||
checkUid,
|
||||
isUname,
|
||||
checkUname,
|
||||
isPassword,
|
||||
checkPassword,
|
||||
isEmail,
|
||||
checkEmail,
|
||||
isContent,
|
||||
checkContent,
|
||||
isName,
|
||||
checkName,
|
||||
isPid,
|
||||
checkPid,
|
||||
};
|
||||
/*
|
||||
ID_RE = re.compile(r'[^\\/\s\u3000]([^\\/\n\r]*[^\\/\s\u3000])?')
|
||||
@@ -104,4 +111,4 @@ def check_domain_invitation_code(s):
|
||||
if not is_domain_invitation_code(s):
|
||||
raise error.ValidationError('invitation_code')
|
||||
|
||||
*/
|
||||
*/
|
||||
@@ -1,9 +1,9 @@
|
||||
const
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('blacklist');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('blacklist');
|
||||
|
||||
async function add(ip) {
|
||||
let expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||
const expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||
return coll.findOneAndUpdate({ _id: ip }, { $set: { expireAt } }, { upsert: true });
|
||||
}
|
||||
function get(ip) {
|
||||
|
||||
+49
-40
@@ -1,20 +1,21 @@
|
||||
const perm = require('../permission');
|
||||
|
||||
const BUILTIN_ROLES = [
|
||||
{ _id: 'guest', perm: perm.PERM_BASIC },
|
||||
{ _id: 'default', perm: perm.PERM_DEFAULT },
|
||||
{ _id: 'admin', perm: perm.PERM_ADMIN }
|
||||
{ _id: 'admin', perm: perm.PERM_ADMIN },
|
||||
];
|
||||
const CATEGORIES = {
|
||||
'动态规划': [
|
||||
动态规划: [
|
||||
'LCS',
|
||||
'LIS',
|
||||
'背包',
|
||||
'单调性DP',
|
||||
'环形DP',
|
||||
'树形DP',
|
||||
'状态压缩DP'
|
||||
'状态压缩DP',
|
||||
],
|
||||
'搜索': [
|
||||
搜索: [
|
||||
'枚举',
|
||||
'搜索与剪枝',
|
||||
'启发式搜索',
|
||||
@@ -22,9 +23,9 @@ const CATEGORIES = {
|
||||
'双向搜索',
|
||||
'折半搜索',
|
||||
'记忆化搜索',
|
||||
'模拟退火'
|
||||
'模拟退火',
|
||||
],
|
||||
'计算几何': [
|
||||
计算几何: [
|
||||
'半平面交',
|
||||
'凸包',
|
||||
'几何图形的交与并',
|
||||
@@ -37,19 +38,19 @@ const CATEGORIES = {
|
||||
'平面图的对偶图',
|
||||
'三角剖分',
|
||||
'梯形剖分',
|
||||
'几何知识'
|
||||
'几何知识',
|
||||
],
|
||||
'贪心': [],
|
||||
'树结构': [
|
||||
贪心: [],
|
||||
树结构: [
|
||||
'最近公共祖先',
|
||||
'生成树',
|
||||
'DFS序列',
|
||||
'树上倍增',
|
||||
'树的分治',
|
||||
'树链剖分',
|
||||
'Link-Cut-Tree'
|
||||
'Link-Cut-Tree',
|
||||
],
|
||||
'图结构': [
|
||||
图结构: [
|
||||
'平面图',
|
||||
'二分图',
|
||||
'二分图匹配',
|
||||
@@ -60,9 +61,9 @@ const CATEGORIES = {
|
||||
'强连通分量',
|
||||
'割点割边',
|
||||
'欧拉回路',
|
||||
'2-SAT'
|
||||
'2-SAT',
|
||||
],
|
||||
'数论': [
|
||||
数论: [
|
||||
'素数判定',
|
||||
'欧几里得算法',
|
||||
'不定方程',
|
||||
@@ -73,10 +74,10 @@ const CATEGORIES = {
|
||||
'大整数质因数分解',
|
||||
'勾股方程',
|
||||
'积性函数',
|
||||
'Fibonacci数列'
|
||||
'Fibonacci数列',
|
||||
],
|
||||
'模拟': [],
|
||||
'数据结构': [
|
||||
模拟: [],
|
||||
数据结构: [
|
||||
'栈',
|
||||
'队列',
|
||||
'链表',
|
||||
@@ -93,41 +94,41 @@ const CATEGORIES = {
|
||||
'k-d树',
|
||||
'块状链表',
|
||||
'Hashing',
|
||||
'函数式编程'
|
||||
'函数式编程',
|
||||
],
|
||||
'博弈论': [],
|
||||
'字符串': [
|
||||
博弈论: [],
|
||||
字符串: [
|
||||
'KMP',
|
||||
'后缀数据结构',
|
||||
'Trie树',
|
||||
'AC自动机',
|
||||
'Manacher',
|
||||
'表达式处理',
|
||||
'最小表示法'
|
||||
'最小表示法',
|
||||
],
|
||||
'组合数学': [
|
||||
组合数学: [
|
||||
'生成函数',
|
||||
'容斥原理',
|
||||
'康托展开',
|
||||
'Catalan数列',
|
||||
'Stirling数',
|
||||
'差分',
|
||||
'Polya定理'
|
||||
'Polya定理',
|
||||
],
|
||||
'线性代数': [
|
||||
线性代数: [
|
||||
'矩阵乘法',
|
||||
'高斯消元',
|
||||
'线性规划'
|
||||
'线性规划',
|
||||
],
|
||||
'高精度': [
|
||||
'FFT'
|
||||
高精度: [
|
||||
'FFT',
|
||||
],
|
||||
'递推': [],
|
||||
'概率论': [
|
||||
'随机化'
|
||||
递推: [],
|
||||
概率论: [
|
||||
'随机化',
|
||||
],
|
||||
'NPC': [],
|
||||
'其他': [
|
||||
NPC: [],
|
||||
其他: [
|
||||
'二分查找',
|
||||
'三分查找',
|
||||
'双指针扫描',
|
||||
@@ -137,15 +138,15 @@ const CATEGORIES = {
|
||||
'快速幂',
|
||||
'数学',
|
||||
'排序',
|
||||
'构造'
|
||||
]
|
||||
'构造',
|
||||
],
|
||||
};
|
||||
const FOOTER_EXTRA_HTMLS = [];
|
||||
const VIEW_LANGS = [
|
||||
{ code: 'zh-CN', name: 'zh-CN' }
|
||||
{ code: 'zh-CN', name: 'zh-CN' },
|
||||
];
|
||||
const LANGS = {
|
||||
cc: 'cpp'
|
||||
cc: 'cpp',
|
||||
};
|
||||
const LANG_TEXTS = {
|
||||
c: 'C',
|
||||
@@ -192,7 +193,7 @@ const STATUS = {
|
||||
STATUS_JUDGING: 20,
|
||||
STATUS_COMPILING: 21,
|
||||
STATUS_FETCHED: 22,
|
||||
STATUS_IGNORED: 30
|
||||
STATUS_IGNORED: 30,
|
||||
};
|
||||
const STATUS_TEXTS = {
|
||||
[STATUS.STATUS_WAITING]: 'Waiting',
|
||||
@@ -226,9 +227,17 @@ const STATUS_CODES = {
|
||||
20: 'progress',
|
||||
21: 'progress',
|
||||
22: 'progress',
|
||||
30: 'ignored'
|
||||
30: 'ignored',
|
||||
};
|
||||
module.exports = {
|
||||
BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS, LANGS,
|
||||
LANG_TEXTS, LANG_HIGHLIGHT_ID, STATUS, STATUS_TEXTS, STATUS_CODES
|
||||
};
|
||||
BUILTIN_ROLES,
|
||||
CATEGORIES,
|
||||
VIEW_LANGS,
|
||||
FOOTER_EXTRA_HTMLS,
|
||||
LANGS,
|
||||
LANG_TEXTS,
|
||||
LANG_HIGHLIGHT_ID,
|
||||
STATUS,
|
||||
STATUS_TEXTS,
|
||||
STATUS_CODES,
|
||||
};
|
||||
+84
-64
@@ -1,18 +1,20 @@
|
||||
const
|
||||
user = require('./user'),
|
||||
problem = require('./problem'),
|
||||
{ ValidationError, ContestNotFoundError, ContestAlreadyAttendedError,
|
||||
ContestNotAttendedError, ProblemNotFoundError, ContestScoreboardHiddenError } = require('../error'),
|
||||
{ PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission'),
|
||||
validator = require('../lib/validator'),
|
||||
db = require('../service/db'),
|
||||
coll = db.collection('contest'),
|
||||
coll_status = db.collection('contest.status');
|
||||
const user = require('./user');
|
||||
const problem = require('./problem');
|
||||
const {
|
||||
ValidationError, ContestNotFoundError, ContestAlreadyAttendedError,
|
||||
ContestNotAttendedError, ProblemNotFoundError, ContestScoreboardHiddenError,
|
||||
} = require('../error');
|
||||
const { PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission');
|
||||
const validator = require('../lib/validator');
|
||||
const db = require('../service/db');
|
||||
|
||||
const coll = db.collection('contest');
|
||||
const collStatus = db.collection('contest.status');
|
||||
|
||||
const RULES = {
|
||||
homework: require('../module/contest/homework'),
|
||||
oi: require('../module/contest/oi'),
|
||||
acm: require('../module/contest/acm')
|
||||
acm: require('../module/contest/acm'),
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -21,13 +23,13 @@ const RULES = {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {number} owner
|
||||
* @param {string} rule
|
||||
* @param {Date} beginAt
|
||||
* @param {Date} endAt
|
||||
* @param {ObjectID[]} pids
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {number} owner
|
||||
* @param {string} rule
|
||||
* @param {Date} beginAt
|
||||
* @param {Date} endAt
|
||||
* @param {ObjectID[]} pids
|
||||
* @param {object} data
|
||||
* @returns {ObjectID} tid
|
||||
*/
|
||||
@@ -37,112 +39,120 @@ async function add(title, content, owner, rule,
|
||||
validator.checkContent(content);
|
||||
if (!this.RULES[rule]) throw new ValidationError('rule');
|
||||
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
|
||||
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
|
||||
Object.assign(data, {
|
||||
content, owner, title, rule, beginAt, endAt, pids, attend: 0,
|
||||
});
|
||||
this.RULES[rule].check(data);
|
||||
let res = await coll.insertOne(data);
|
||||
const res = await coll.insertOne(data);
|
||||
return res.insertedId;
|
||||
}
|
||||
/**
|
||||
* @param {ObjectID} tid
|
||||
* @param {object} $set
|
||||
* @param {ObjectID} tid
|
||||
* @param {object} $set
|
||||
* @returns {Tdoc} tdoc after modification
|
||||
*/
|
||||
async function edit(tid, $set) {
|
||||
if ($set.title) validator.checkTitle($set.title);
|
||||
if ($set.content) validator.checkIntro($set.content);
|
||||
if ($set.rule)
|
||||
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
|
||||
if ($set.beginAt && $set.endAt)
|
||||
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
|
||||
let tdoc = await coll.findOne({ tid });
|
||||
if ($set.rule) { if (!this.RULES[$set.rule]) throw new ValidationError('rule'); }
|
||||
if ($set.beginAt && $set.endAt) if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
|
||||
const tdoc = await coll.findOne({ tid });
|
||||
if (!tdoc) throw new ContestNotFoundError(tid);
|
||||
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
|
||||
await coll.findOneAndUpdate({ tid }, { $set });
|
||||
return tdoc;
|
||||
}
|
||||
/**
|
||||
* @param {ObjectID} tid
|
||||
* @param {ObjectID} tid
|
||||
* @returns {Tdoc}
|
||||
*/
|
||||
async function get(tid) {
|
||||
let tdoc = await coll.findOne({ _id: tid });
|
||||
const tdoc = await coll.findOne({ _id: tid });
|
||||
if (!tdoc) throw new ContestNotFoundError(tid);
|
||||
return tdoc;
|
||||
}
|
||||
async function updateStatus(tid, uid, rid, pid, accept, score) {
|
||||
let tdoc = await get(tid);
|
||||
let tsdoc = await coll_status.findOneAndUpdate({ tid: tdoc._id, uid }, {
|
||||
$push: { journal: { rid, pid, accept, score } },
|
||||
$inc: { rev: 1 }
|
||||
const tdoc = await get(tid);
|
||||
const tsdoc = await collStatus.findOneAndUpdate({ tid: tdoc._id, uid }, {
|
||||
$push: {
|
||||
journal: {
|
||||
rid, pid, accept, score,
|
||||
},
|
||||
},
|
||||
$inc: { rev: 1 },
|
||||
}, { upsert: true });
|
||||
if (!tsdoc.value.attend) throw new ContestNotAttendedError(tid, uid);
|
||||
}
|
||||
async function getListStatus(uid, tids) {
|
||||
let r = {};
|
||||
for (let tid of tids) r[tid] = await this.getStatus(tid, uid);
|
||||
const r = {};
|
||||
for (const tid of tids) r[tid] = await this.getStatus(tid, uid); // eslint-disable-line no-await-in-loop
|
||||
return r;
|
||||
}
|
||||
async function attend(tid, uid) {
|
||||
try {
|
||||
await coll_status.insertOne({ tid, uid, attend: 1 });
|
||||
await collStatus.insertOne({ tid, uid, attend: 1 });
|
||||
} catch (e) {
|
||||
throw new ContestAlreadyAttendedError(tid, uid);
|
||||
}
|
||||
await coll.findOneAndUpdate({ _id: tid }, { $inc: { attend: 1 } });
|
||||
}
|
||||
function getMultiStatus(query) {
|
||||
return coll_status.find(query);
|
||||
return collStatus.find(query);
|
||||
}
|
||||
function is_new(tdoc, days = 1) {
|
||||
let now = new Date().getTime();
|
||||
let readyAt = tdoc.beginAt.getTime();
|
||||
const now = new Date().getTime();
|
||||
const readyAt = tdoc.beginAt.getTime();
|
||||
return (now < readyAt - days * 24 * 3600 * 1000);
|
||||
}
|
||||
function is_upcoming(tdoc, days = 1) {
|
||||
let now = new Date().getTime();
|
||||
let readyAt = tdoc.beginAt.getTime();
|
||||
const now = new Date().getTime();
|
||||
const readyAt = tdoc.beginAt.getTime();
|
||||
return (now > readyAt - days * 24 * 3600 * 1000 && now < tdoc.beginAt);
|
||||
}
|
||||
function is_not_started(tdoc) {
|
||||
return (new Date()) < tdoc.beginAt;
|
||||
}
|
||||
function is_ongoing(tdoc) {
|
||||
let now = new Date();
|
||||
const now = new Date();
|
||||
return (tdoc.beginAt <= now && now < tdoc.endAt);
|
||||
}
|
||||
function is_done(tdoc) {
|
||||
return tdoc.endAt <= new Date();
|
||||
}
|
||||
|
||||
const ContestHandlerMixin = c => class extends c {
|
||||
const ContestHandlerMixin = (c) => class extends c {
|
||||
canViewHiddenScoreboard() {
|
||||
return this.user.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
|
||||
canShowRecord(tdoc, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
canShowScoreboard(tdoc, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async getScoreboard(tid, isExport = false) {
|
||||
let tdoc = await get(tid);
|
||||
const tdoc = await get(tid);
|
||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
let tsdocs = await getMultiStatus(tid).sort(RULES[tdoc.rule].statusSort).toArray();
|
||||
let uids = [];
|
||||
for (let tsdoc of tsdocs) uids.push(tsdoc.uid);
|
||||
let [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc['pids'])]);
|
||||
let ranked_tsdocs = RULES[tdoc.rule].rank(tsdocs);
|
||||
let rows = RULES[tdoc.rule].scoreboard(isExport, str => str ? str.toString().translate(this.user.language) : '', tdoc, ranked_tsdocs, udict, pdict);
|
||||
const tsdocs = await getMultiStatus(tid).sort(RULES[tdoc.rule].statusSort).toArray();
|
||||
const uids = [];
|
||||
for (const tsdoc of tsdocs) uids.push(tsdoc.uid);
|
||||
const [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc.pids)]);
|
||||
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
|
||||
const rows = RULES[tdoc.rule].scoreboard(isExport, (str) => (str ? str.toString().translate(this.user.language) : ''), tdoc, rankedTsdocs, udict, pdict);
|
||||
return [tdoc, rows, udict];
|
||||
}
|
||||
async verifyProblems(pids) {
|
||||
let r = [];
|
||||
for (let pid of pids) {
|
||||
let res = await problem.get(pid);
|
||||
|
||||
async verifyProblems(pids) { // eslint-disable-line class-methods-use-this
|
||||
const r = [];
|
||||
for (const pid of pids) {
|
||||
const res = await problem.get(pid); // eslint-disable-line no-await-in-loop
|
||||
if (res) r.push(res._id);
|
||||
else throw new ProblemNotFoundError(pid);
|
||||
}
|
||||
@@ -151,26 +161,37 @@ const ContestHandlerMixin = c => class extends c {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
RULES, ContestHandlerMixin, add, getListStatus, attend, edit, get, updateStatus,
|
||||
count: query => coll.find(query).count(),
|
||||
getMulti: query => coll.find(query),
|
||||
RULES,
|
||||
ContestHandlerMixin,
|
||||
add,
|
||||
getListStatus,
|
||||
attend,
|
||||
edit,
|
||||
get,
|
||||
updateStatus,
|
||||
count: (query) => coll.find(query).count(),
|
||||
getMulti: (query) => coll.find(query),
|
||||
getStatus: (tid, uid) => coll_status.findOne({ tid, uid }),
|
||||
setStatus: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set }),
|
||||
is_new, is_upcoming, is_not_started, is_ongoing, is_done,
|
||||
status_text: tdoc =>
|
||||
is_new,
|
||||
is_upcoming,
|
||||
is_not_started,
|
||||
is_ongoing,
|
||||
is_done,
|
||||
status_text: (tdoc) => (
|
||||
is_new(tdoc)
|
||||
? 'New'
|
||||
: is_upcoming(tdoc)
|
||||
? 'Ready (☆▽☆)'
|
||||
: is_ongoing(tdoc)
|
||||
? 'Live...'
|
||||
: 'Done',
|
||||
get_status: tdoc =>
|
||||
: 'Done'),
|
||||
get_status: (tdoc) => (
|
||||
is_not_started(tdoc)
|
||||
? 'not_started'
|
||||
: is_ongoing(tdoc)
|
||||
? 'ongoing'
|
||||
: 'finished',
|
||||
: 'finished'),
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -336,7 +357,6 @@ def _format_pids(pids_list):
|
||||
return ','.join([str(pid) for pid in pids_list])
|
||||
|
||||
|
||||
|
||||
class ContestStatusMixin(object):
|
||||
@property
|
||||
@functools.lru_cache()
|
||||
@@ -447,4 +467,4 @@ class ContestCommonOperationMixin(object):
|
||||
class ContestMixin(ContestStatusMixin, ContestVisibilityMixin, ContestCommonOperationMixin):
|
||||
pass
|
||||
|
||||
*/
|
||||
*/
|
||||
@@ -9,5 +9,5 @@ module.exports = {
|
||||
system: require('./system'),
|
||||
token: require('./token'),
|
||||
training: require('./training'),
|
||||
user: require('./user')
|
||||
user: require('./user'),
|
||||
};
|
||||
+18
-25
@@ -1,35 +1,28 @@
|
||||
const
|
||||
{ OpcountExceededError } = require('../error'),
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('opcount');
|
||||
const { OpcountExceededError } = require('../error');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('opcount');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {string} op
|
||||
* @param {string} ident
|
||||
* @param {number} period_secs
|
||||
* @param {number} max_operations
|
||||
* @param {string} op
|
||||
* @param {string} ident
|
||||
* @param {number} period_secs
|
||||
* @param {number} max_operations
|
||||
*/
|
||||
async inc(op, ident, period_secs, max_operations) {
|
||||
let cur_time = new Date().getTime();
|
||||
let begin_at = new Date(cur_time - cur_time % (period_secs * 1000));
|
||||
let expire_at = new Date(begin_at.getTime() + period_secs * 1000);
|
||||
async inc(op, ident, periodSecs, maxOperations) {
|
||||
const curTime = new Date().getTime();
|
||||
const beginAt = new Date(curTime - (curTime % (periodSecs * 1000)));
|
||||
const expireAt = new Date(beginAt.getTime() + periodSecs * 1000);
|
||||
try {
|
||||
await coll.findOneAndUpdate({
|
||||
ident, begin_at, expire_at,
|
||||
op: { $not: { $gte: max_operations } }
|
||||
ident,
|
||||
beginAt,
|
||||
expireAt,
|
||||
op: { $not: { $gte: maxOperations } },
|
||||
}, { $inc: { op: 1 } }, { upsert: true });
|
||||
} catch (e) {
|
||||
throw new OpcountExceededError(op, period_secs, max_operations);
|
||||
throw new OpcountExceededError(op, periodSecs, maxOperations);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
/*
|
||||
@argmethod.wrap
|
||||
async def ensure_indexes():
|
||||
coll = db.coll('opcount')
|
||||
await coll.create_index([('ident', 1),
|
||||
('begin_at', 1),
|
||||
('expire_at', 1)], unique=True)
|
||||
await coll.create_index('expire_at', expireAfterSeconds=0)
|
||||
*/
|
||||
+34
-37
@@ -1,10 +1,10 @@
|
||||
const
|
||||
{ ObjectID } = require('bson'),
|
||||
{ ProblemNotFoundError } = require('../error'),
|
||||
validator = require('../lib/validator'),
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('problem'),
|
||||
coll_status = db.collection('problem.status');
|
||||
const { ObjectID } = require('bson');
|
||||
const { ProblemNotFoundError } = require('../error');
|
||||
const validator = require('../lib/validator');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('problem');
|
||||
const collStatus = db.collection('problem.status');
|
||||
|
||||
/**
|
||||
* @typedef {import('../interface').Pdoc} Pdoc
|
||||
@@ -13,14 +13,14 @@ const
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {number} owner
|
||||
* @param {number} pid
|
||||
* @param {import('bson').ObjectID} data
|
||||
* @param {string[]} category
|
||||
* @param {string[]} tag
|
||||
* @param {boolean} hidden
|
||||
* @param {string} title
|
||||
* @param {string} content
|
||||
* @param {number} owner
|
||||
* @param {number} pid
|
||||
* @param {import('bson').ObjectID} data
|
||||
* @param {string[]} category
|
||||
* @param {string[]} tag
|
||||
* @param {boolean} hidden
|
||||
*/
|
||||
async function add({
|
||||
title,
|
||||
@@ -30,7 +30,7 @@ async function add({
|
||||
data = null,
|
||||
category = [],
|
||||
tag = [],
|
||||
hidden = false
|
||||
hidden = false,
|
||||
}) {
|
||||
validator.checkTitle(title);
|
||||
validator.checkContent(content);
|
||||
@@ -44,7 +44,7 @@ async function add({
|
||||
tag,
|
||||
hidden,
|
||||
nSubmit: 0,
|
||||
nAccept: 0
|
||||
nAccept: 0,
|
||||
});
|
||||
return pid;
|
||||
}
|
||||
@@ -55,13 +55,13 @@ async function add({
|
||||
*/
|
||||
async function get(pid, uid = null) {
|
||||
let query = {};
|
||||
if (pid.generationTime || pid.length == 24) query = { _id: new ObjectID(pid) };
|
||||
if (pid.generationTime || pid.length === 24) query = { _id: new ObjectID(pid) };
|
||||
else query = { pid: parseInt(pid) || pid };
|
||||
let pdoc = await coll.findOne(query);
|
||||
const pdoc = await coll.findOne(query);
|
||||
if (!pdoc) throw new ProblemNotFoundError(pid);
|
||||
if (uid) {
|
||||
query.uid = uid;
|
||||
pdoc.psdoc = await coll_status.findOne(query);
|
||||
pdoc.psdoc = await collStatus.findOne(query);
|
||||
}
|
||||
return pdoc;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ async function get(pid, uid = null) {
|
||||
*/
|
||||
async function getById(_id) {
|
||||
_id = new ObjectID(_id);
|
||||
let pdoc = await coll.findOne({ _id });
|
||||
const pdoc = await coll.findOne({ _id });
|
||||
if (!pdoc) throw new ProblemNotFoundError(_id);
|
||||
return pdoc;
|
||||
}
|
||||
@@ -81,7 +81,8 @@ async function getById(_id) {
|
||||
* @returns {Pdoc[]}
|
||||
*/
|
||||
function getMany(query, sort, page, limit) {
|
||||
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
|
||||
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
|
||||
.toArray();
|
||||
}
|
||||
/**
|
||||
* @param {object} query
|
||||
@@ -99,28 +100,24 @@ async function edit(_id, $set) {
|
||||
if ($set.title) validator.checkTitle($set.title);
|
||||
if ($set.content) validator.checkContent($set.content);
|
||||
await coll.findOneAndUpdate({ _id }, { $set });
|
||||
let pdoc = await getById(_id);
|
||||
const pdoc = await getById(_id);
|
||||
if (!pdoc) throw new ProblemNotFoundError(_id);
|
||||
return pdoc;
|
||||
}
|
||||
/**
|
||||
* @param {object} query
|
||||
* @returns {number}
|
||||
*/
|
||||
async function count(query) {
|
||||
return await coll.find(query).count();
|
||||
function count(query) {
|
||||
return coll.find(query).count();
|
||||
}
|
||||
async function random(query) {
|
||||
let pdocs = coll.find(query);
|
||||
let pcount = await pdocs.count();
|
||||
const pdocs = coll.find(query);
|
||||
const pcount = await pdocs.count();
|
||||
if (pcount) {
|
||||
let pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0];
|
||||
const pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0];
|
||||
return pdoc.pid;
|
||||
} else return null;
|
||||
} return null;
|
||||
}
|
||||
async function getList(pids) {
|
||||
let r = {};
|
||||
for (let pid of pids) r[pid] = await get(pid);
|
||||
const r = {};
|
||||
for (const pid of pids) r[pid] = await get(pid); // eslint-disable-line no-await-in-loop
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -133,5 +130,5 @@ module.exports = {
|
||||
random,
|
||||
getById,
|
||||
getMulti,
|
||||
getList
|
||||
};
|
||||
getList,
|
||||
};
|
||||
+27
-26
@@ -1,13 +1,13 @@
|
||||
const
|
||||
_ = require('lodash'),
|
||||
{ ObjectID } = require('bson'),
|
||||
{ RecordNotFoundError } = require('../error'),
|
||||
{ STATUS_WAITING } = require('../model/builtin').STATUS,
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('record');
|
||||
const _ = require('lodash');
|
||||
const { ObjectID } = require('bson');
|
||||
const { RecordNotFoundError } = require('../error');
|
||||
const { STATUS_WAITING } = require('./builtin').STATUS;
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('record');
|
||||
|
||||
/**
|
||||
* @param {import('../interface').Record} data
|
||||
* @param {import('../interface').Record} data
|
||||
*/
|
||||
async function add(data) {
|
||||
_.defaults(data, {
|
||||
@@ -20,33 +20,34 @@ async function add(data) {
|
||||
compilerTexts: [],
|
||||
testCases: [],
|
||||
judger: null,
|
||||
judgeAt: null
|
||||
judgeAt: null,
|
||||
});
|
||||
let res = await coll.insertOne(data);
|
||||
const res = await coll.insertOne(data);
|
||||
return res.insertedId;
|
||||
}
|
||||
/**
|
||||
* @param {string} rid
|
||||
* @param {string} rid
|
||||
* @returns {import('../interface').Record}
|
||||
*/
|
||||
async function get(rid) {
|
||||
let _id = new ObjectID(rid);
|
||||
let rdoc = await coll.findOne({ _id });
|
||||
const _id = new ObjectID(rid);
|
||||
const rdoc = await coll.findOne({ _id });
|
||||
if (!rdoc) throw new RecordNotFoundError(rid);
|
||||
return rdoc;
|
||||
}
|
||||
async function getMany(query, sort, page, limit) {
|
||||
return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
|
||||
function getMany(query, sort, page, limit) {
|
||||
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
|
||||
.toArray();
|
||||
}
|
||||
async function update(rid, $set) {
|
||||
let _id = new ObjectID(rid);
|
||||
const _id = new ObjectID(rid);
|
||||
await coll.findOneAndUpdate({ _id }, { $set });
|
||||
let rdoc = await coll.findOne({ _id });
|
||||
const rdoc = await coll.findOne({ _id });
|
||||
if (!rdoc) throw new RecordNotFoundError(rid);
|
||||
return rdoc;
|
||||
}
|
||||
async function reset(rid) {
|
||||
return await update(rid, {
|
||||
function reset(rid) {
|
||||
return update(rid, {
|
||||
score: 0,
|
||||
status: STATUS_WAITING,
|
||||
time: 0,
|
||||
@@ -56,15 +57,15 @@ async function reset(rid) {
|
||||
compilerTexts: [],
|
||||
judgeAt: null,
|
||||
judger: null,
|
||||
rejudged: true
|
||||
rejudged: true,
|
||||
});
|
||||
}
|
||||
async function count(query) {
|
||||
return await coll.find(query).count();
|
||||
function count(query) {
|
||||
return coll.find(query).count();
|
||||
}
|
||||
async function getList(rids) {
|
||||
let r = {};
|
||||
for (let rid of rids) r[rid] = await get(rid);
|
||||
const r = {};
|
||||
for (const rid of rids) r[rid] = await get(rid); // eslint-disable-line no-await-in-loop
|
||||
return r;
|
||||
}
|
||||
function getUserInProblemMulti(uid, pid) {
|
||||
@@ -79,5 +80,5 @@ module.exports = {
|
||||
count,
|
||||
reset,
|
||||
getList,
|
||||
getUserInProblemMulti
|
||||
};
|
||||
getUserInProblemMulti,
|
||||
};
|
||||
+46
-45
@@ -1,93 +1,94 @@
|
||||
const
|
||||
{ ObjectID } = require('bson'),
|
||||
{ SolutionNotFoundError, AlreadyVotedError } = require('../error'),
|
||||
validator = require('../lib/validator'),
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('solution'),
|
||||
coll_status = db.collection('solution.status');
|
||||
const { ObjectID } = require('bson');
|
||||
const { SolutionNotFoundError } = require('../error');
|
||||
const validator = require('../lib/validator');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
/**
|
||||
const coll = db.collection('solution');
|
||||
const collStatus = db.collection('solution.status');
|
||||
|
||||
/**
|
||||
* @param {string} pid
|
||||
* @param {number} owner
|
||||
* @param {string} content
|
||||
* @param {number} owner
|
||||
* @param {string} content
|
||||
*/
|
||||
async function add(pid, owner, content) {
|
||||
validator.checkContent(content);
|
||||
pid = new ObjectID(pid);
|
||||
let res = await coll.insertOne({ content, owner, pid, reply: [], vote: 0 });
|
||||
const res = await coll.insertOne({
|
||||
content, owner, pid, reply: [], vote: 0,
|
||||
});
|
||||
return res.insertedId;
|
||||
}
|
||||
async function get(psid) {
|
||||
psid = new ObjectID(psid);
|
||||
let psdoc = await coll.findOne({ _id: psid });
|
||||
const psdoc = await coll.findOne({ _id: psid });
|
||||
if (!psdoc) throw new SolutionNotFoundError();
|
||||
return psdoc;
|
||||
}
|
||||
async function getMany(query, sort, page, limit) {
|
||||
return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
|
||||
function getMany(query, sort, page, limit) {
|
||||
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
|
||||
.toArray();
|
||||
}
|
||||
async function edit(_id, content) {
|
||||
validator.checkContent(content);
|
||||
await coll.findOneAndUpdate({ _id }, { $set: { content } });
|
||||
let psdoc = await get(_id);
|
||||
const psdoc = await get(_id);
|
||||
if (!psdoc) throw new SolutionNotFoundError(_id);
|
||||
return psdoc;
|
||||
}
|
||||
async function del(psid) {
|
||||
psid = new ObjectID(psid);
|
||||
return await coll.deleteOne({ _id: psid });
|
||||
function del(psid) {
|
||||
return coll.deleteOne({ _id: psid });
|
||||
}
|
||||
async function count(query) {
|
||||
return await coll.find(query).count();
|
||||
function count(query) {
|
||||
return coll.find(query).count();
|
||||
}
|
||||
function getMulti(pid) {
|
||||
return coll.find({ pid }).sort({ vote: -1 });
|
||||
}
|
||||
async function reply(psid, owner, content) {
|
||||
psid = new ObjectID(psid);
|
||||
function reply(psid, owner, content) {
|
||||
validator.checkContent(content);
|
||||
return await coll.findOneAndUpdate({ _id: psid }, { $push: { reply: { content, owner, _id: new ObjectID() } } });
|
||||
return coll.findOneAndUpdate(
|
||||
{ _id: psid },
|
||||
{ $push: { reply: { content, owner, _id: new ObjectID() } } },
|
||||
);
|
||||
}
|
||||
async function getReply(psid, psrid) {
|
||||
psid = new ObjectID(psid);
|
||||
psrid = new ObjectID(psrid);
|
||||
let psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
const psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
if (!psdoc) return [null, null];
|
||||
for (let psrdoc of psdoc)
|
||||
if (psrdoc._id == psrid)
|
||||
return [psdoc, psrdoc];
|
||||
for (const psrdoc of psdoc) if (psrdoc._id === psrid) return [psdoc, psrdoc];
|
||||
return [psdoc, null];
|
||||
}
|
||||
async function editReply(psid, psrid, content) {
|
||||
validator.checkContent(content);
|
||||
psid = new ObjectID(psid);
|
||||
psrid = new ObjectID(psrid);
|
||||
let psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
let reply = psdoc.reply;
|
||||
for (let i in reply)
|
||||
if (reply[i]._id == psrid) {
|
||||
const psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
const { reply } = psdoc;
|
||||
for (const i in reply) {
|
||||
if (reply[i]._id === psrid) {
|
||||
reply[i].content = content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return await coll.findOneAndUpdate({ _id: psdoc._id }, { $set: { reply } });
|
||||
}
|
||||
async function delReply(psid, psrid) {
|
||||
return await coll.findOneAndUpdate({ _id: psid }, { $pull: { reply: { _id: psrid } } });
|
||||
function delReply(psid, psrid) {
|
||||
return coll.findOneAndUpdate({ _id: psid }, { $pull: { reply: { _id: psrid } } });
|
||||
}
|
||||
async function vote(psid, uid, value) {
|
||||
let pssdoc = await coll_status.findOne({ psid, uid });
|
||||
if (pssdoc) await coll_status.deleteOne({ psid, uid });
|
||||
await coll_status.insertOne({ psid, uid, vote: value });
|
||||
let pssdoc = await collStatus.findOne({ psid, uid });
|
||||
if (pssdoc) await collStatus.deleteOne({ psid, uid });
|
||||
await collStatus.insertOne({ psid, uid, vote: value });
|
||||
if (pssdoc) value += -pssdoc.vote;
|
||||
await coll.findOneAndUpdate({ _id: psid }, { $inc: { vote: value } });
|
||||
pssdoc = await coll_status.findOne({ psid, uid });
|
||||
let psdoc = await coll.findOne({ _id: psid });
|
||||
pssdoc = await collStatus.findOne({ psid, uid });
|
||||
const psdoc = await coll.findOne({ _id: psid });
|
||||
return [psdoc, pssdoc];
|
||||
}
|
||||
async function getListStatus(list, uid) {
|
||||
let result = {};
|
||||
let res = await coll_status.find({ uid, psid: { $in: list } }).toArray();
|
||||
for (let i of res) result[i.psid] = i;
|
||||
const result = {};
|
||||
const res = await collStatus.find({ uid, psid: { $in: list } }).toArray();
|
||||
for (const i of res) result[i.psid] = i;
|
||||
return result;
|
||||
}
|
||||
module.exports = {
|
||||
@@ -103,5 +104,5 @@ module.exports = {
|
||||
editReply,
|
||||
delReply,
|
||||
vote,
|
||||
getListStatus
|
||||
};
|
||||
getListStatus,
|
||||
};
|
||||
+15
-82
@@ -1,42 +1,43 @@
|
||||
const
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('system');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('system');
|
||||
|
||||
async function get(_id) {
|
||||
let doc = await coll.findOne({ _id });
|
||||
const doc = await coll.findOne({ _id });
|
||||
if (doc) return doc.value;
|
||||
return null;
|
||||
}
|
||||
async function update(_id, update, config) {
|
||||
await coll.findOneAndUpdate({ _id }, update, config);
|
||||
return this.get(_id);
|
||||
return get(_id);
|
||||
}
|
||||
/**
|
||||
* Increments the user counter.
|
||||
* @returns {number} Integer value after increment.
|
||||
*/
|
||||
async function incUserCounter() {
|
||||
return await this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
|
||||
function incUserCounter() {
|
||||
return update('userCounter', { $inc: { value: 1 } }, { upsert: true });
|
||||
}
|
||||
/**
|
||||
* Increments the problem ID counter.
|
||||
* @returns {number} Integer value before increment.
|
||||
*/
|
||||
async function incPidCounter() {
|
||||
return await this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
|
||||
function incPidCounter() {
|
||||
return update('userCounter', { $inc: { value: 1 } }, { upsert: true });
|
||||
}
|
||||
async function acquireLock(name) {
|
||||
let value = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
const value = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
try {
|
||||
await coll.updateOne({ _id: 'lock_' + name, value: 0 }, { $set: { value } }, { upsert: true });
|
||||
await coll.updateOne({ _id: `lock_${name}`, value: 0 }, { $set: { value } }, { upsert: true });
|
||||
} catch (e) { return null; }
|
||||
return value;
|
||||
}
|
||||
async function releaseLock(name, value) {
|
||||
let result = await coll.updateOne({ _id: 'lock_' + name, value }, { $set: { value: 0 } });
|
||||
const result = await coll.updateOne({ _id: `lock_${name}`, value }, { $set: { value: 0 } });
|
||||
return !!result.matchedCount;
|
||||
}
|
||||
async function releaseLockAnyway(name) {
|
||||
await coll.updateOne({ _id: 'lock_' + name }, { $set: { value: 0 } });
|
||||
await coll.updateOne({ _id: `lock_${name}` }, { $set: { value: 0 } });
|
||||
return true;
|
||||
}
|
||||
module.exports = {
|
||||
@@ -46,73 +47,5 @@ module.exports = {
|
||||
incUserCounter,
|
||||
acquireLock,
|
||||
releaseLock,
|
||||
releaseLockAnyway
|
||||
releaseLockAnyway,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
EXPECTED_DB_VERSION = 1
|
||||
|
||||
|
||||
async def acquire_upgrade_lock():
|
||||
lock = await acquire_lock('upgrade')
|
||||
if not lock:
|
||||
raise error.UpgradeLockAcquireError()
|
||||
return lock
|
||||
|
||||
|
||||
async def release_upgrade_lock(lock: int):
|
||||
success = await release_lock('upgrade', lock)
|
||||
if not success:
|
||||
raise error.UpgradeLockReleaseError()
|
||||
return True
|
||||
|
||||
|
||||
@argmethod.wrap
|
||||
async def release_upgrade_lock_anyway():
|
||||
return await release_lock_anyway('upgrade')
|
||||
|
||||
|
||||
@argmethod.wrap
|
||||
async def get_db_version():
|
||||
coll = db.coll('system')
|
||||
doc = await coll.find_one({'_id': 'db_version'})
|
||||
if doc is None:
|
||||
return 0
|
||||
else:
|
||||
return doc['value']
|
||||
|
||||
|
||||
async def set_db_version(version: int):
|
||||
coll = db.coll('system')
|
||||
result = await coll.update_one(filter={'_id': 'db_version'},
|
||||
update={'$set': {'value': version}},
|
||||
upsert=True)
|
||||
return result.modified_count
|
||||
|
||||
|
||||
async def ensure_db_version(allowed_version=None):
|
||||
if allowed_version is None:
|
||||
allowed_version = EXPECTED_DB_VERSION
|
||||
current_version = await get_db_version()
|
||||
if current_version != allowed_version:
|
||||
raise error.DatabaseVersionMismatchError(current_version, allowed_version)
|
||||
|
||||
|
||||
@argmethod.wrap
|
||||
async def setup():
|
||||
"""
|
||||
Set up for fresh install
|
||||
"""
|
||||
coll = db.coll('system')
|
||||
fdoc = await coll.find_one({'_id': 'user_counter'})
|
||||
if fdoc:
|
||||
# skip if not fresh install
|
||||
return
|
||||
await set_db_version(EXPECTED_DB_VERSION)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
argmethod.invoke_by_args()
|
||||
|
||||
*/
|
||||
+20
-17
@@ -1,6 +1,6 @@
|
||||
const
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('token');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('token');
|
||||
|
||||
module.exports = {
|
||||
TYPE_SESSION: 0,
|
||||
@@ -14,15 +14,16 @@ module.exports = {
|
||||
* @returns {Array} token ID, token data
|
||||
*/
|
||||
async add(tokenType, expireSeconds, data) {
|
||||
let now = new Date();
|
||||
let str = String.random(32);
|
||||
let res = await coll.insertOne(Object.assign({}, data, {
|
||||
const now = new Date();
|
||||
const str = String.random(32);
|
||||
const res = await coll.insertOne({
|
||||
...data,
|
||||
_id: str,
|
||||
tokenType,
|
||||
createAt: now,
|
||||
updateAt: now,
|
||||
expireAt: new Date(now.getTime() + expireSeconds * 1000)
|
||||
}));
|
||||
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
||||
});
|
||||
return [str, res.ops];
|
||||
},
|
||||
|
||||
@@ -45,12 +46,14 @@ module.exports = {
|
||||
* @returns {object} The token document, or null.
|
||||
*/
|
||||
async update(tokenId, tokenType, expireSeconds, data) {
|
||||
let now = new Date();
|
||||
let res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
|
||||
$set: Object.assign({
|
||||
const now = new Date();
|
||||
const res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
|
||||
$set: {
|
||||
updateAt: now,
|
||||
expireAt: new Date(now.getTime() + expireSeconds * 1000)
|
||||
}, data, { tokenType })
|
||||
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
||||
...data,
|
||||
tokenType,
|
||||
},
|
||||
});
|
||||
return res.value;
|
||||
},
|
||||
@@ -62,10 +65,10 @@ module.exports = {
|
||||
* @returns {boolean} true if deleted, or false.
|
||||
*/
|
||||
async delete(tokenId, tokenType) {
|
||||
let result = await coll.deleteOne({ _id: tokenId, tokenType });
|
||||
const result = await coll.deleteOne({ _id: tokenId, tokenType });
|
||||
return !!result.deletedCount;
|
||||
},
|
||||
async getMostRecentSessionByUid(uid) {
|
||||
return await coll.findOne({ uid, token_type: this.TYPE_SESSION }, { sort: { updateAt: -1 } });
|
||||
}
|
||||
getMostRecentSessionByUid(uid) {
|
||||
return coll.findOne({ uid, token_type: this.TYPE_SESSION }, { sort: { updateAt: -1 } });
|
||||
},
|
||||
};
|
||||
+25
-27
@@ -1,26 +1,27 @@
|
||||
const
|
||||
validator = require('../lib/validator'),
|
||||
{ ValidationError, TrainingNotFoundError } = require('../error'),
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('traning'),
|
||||
coll_status = db.collection('training.status');
|
||||
const validator = require('../lib/validator');
|
||||
const { ValidationError, TrainingNotFoundError } = require('../error');
|
||||
const db = require('../service/db.js');
|
||||
|
||||
const coll = db.collection('traning');
|
||||
const collStatus = db.collection('training.status');
|
||||
|
||||
module.exports = {
|
||||
SETTING_DIFFICULTY_ALGORITHM: 0,
|
||||
SETTING_DIFFICULTY_ADMIN: 1,
|
||||
SETTING_DIFFICULTY_AVERAGE: 2,
|
||||
|
||||
async add(title, content, owner_uid, dag = [], desc = '') {
|
||||
validator.check_title(title);
|
||||
validator.check_intro(content);
|
||||
validator.check_description(desc);
|
||||
for (let node of dag)
|
||||
for (let nid of node['require_nids'])
|
||||
if (nid >= node._id)
|
||||
throw new ValidationError('dag');
|
||||
async add(title, content, owner, dag = [], desc = '') {
|
||||
validator.checkTitle(title);
|
||||
validator.checkIntro(content);
|
||||
validator.checkDescription(desc);
|
||||
for (const node of dag) { for (const nid of node.require_nids) if (nid >= node._id) throw new ValidationError('dag'); }
|
||||
return await coll.insertOne({
|
||||
content, owner_uid,
|
||||
dag, title, desc, enroll: 0
|
||||
content,
|
||||
owner,
|
||||
dag,
|
||||
title,
|
||||
desc,
|
||||
enroll: 0,
|
||||
});
|
||||
},
|
||||
count: (query) => coll.find(query).count(),
|
||||
@@ -28,24 +29,21 @@ module.exports = {
|
||||
if ($set.title) validator.check_title($set.title);
|
||||
if ($set.content) validator.check_intro($set.content);
|
||||
if ($set.desc) validator.check_description($set.desc);
|
||||
if ($set.dag)
|
||||
for (let node of $set.dag)
|
||||
for (let nid of node.require_nids)
|
||||
if (nid >= node._id) throw new ValidationError('dag');
|
||||
if ($set.dag) { for (const node of $set.dag) for (const nid of node.require_nids) if (nid >= node._id) throw new ValidationError('dag'); }
|
||||
await coll.findOneAndUpdate({ tid }, { $set });
|
||||
let tdoc = await coll.findOne({ tid });
|
||||
const tdoc = await coll.findOne({ tid });
|
||||
if (!tdoc) throw new TrainingNotFoundError(tid);
|
||||
return tdoc;
|
||||
},
|
||||
async get(tid) {
|
||||
let tdoc = await coll.findOne({ tid });
|
||||
const tdoc = await coll.findOne({ tid });
|
||||
if (!tdoc) throw new TrainingNotFoundError(tid);
|
||||
return tdoc;
|
||||
},
|
||||
get_multi: (query) => coll.find( query),
|
||||
get_multi_status: (query) => coll_status.find(query),
|
||||
get_status: (tid, uid) => coll_status.findOne({ tid, uid }),
|
||||
set_status: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set })
|
||||
get_multi: (query) => coll.find(query),
|
||||
get_multi_status: (query) => collStatus.find(query),
|
||||
get_status: (tid, uid) => collStatus.findOne({ tid, uid }),
|
||||
set_status: (tid, uid, $set) => collStatus.findOneAndUpdate({ tid, uid }, { $set }),
|
||||
|
||||
};
|
||||
|
||||
@@ -66,4 +64,4 @@ async def get_dict(domainId, tids, *, fields=None):
|
||||
fields=fields):
|
||||
result[tdoc['doc_id']] = tdoc
|
||||
return result
|
||||
*/
|
||||
*/
|
||||
+51
-42
@@ -1,11 +1,11 @@
|
||||
const
|
||||
system = require('./system'),
|
||||
{ UserNotFoundError, UserAlreadyExistError } = require('../error'),
|
||||
pwhash = require('../lib/pwhash'),
|
||||
validator = require('../lib/validator'),
|
||||
db = require('../service/db'),
|
||||
coll = db.collection('user'),
|
||||
coll_role = db.collection('role');
|
||||
const system = require('./system');
|
||||
const { UserNotFoundError, UserAlreadyExistError } = require('../error');
|
||||
const pwhash = require('../lib/pwhash');
|
||||
const validator = require('../lib/validator');
|
||||
const db = require('../service/db');
|
||||
|
||||
const coll = db.collection('user');
|
||||
const collRole = db.collection('role');
|
||||
|
||||
class USER {
|
||||
constructor(user) {
|
||||
@@ -24,75 +24,78 @@ class USER {
|
||||
this.nAccept = user.nAccept || 0;
|
||||
this.nSubmit = user.nSubmit || 0;
|
||||
}
|
||||
|
||||
hasPerm(perm) {
|
||||
return this.perm == '-' || (this.perm || '').includes(perm);
|
||||
return this.perm === '-' || (this.perm || '').includes(perm);
|
||||
}
|
||||
|
||||
checkPassword(password) {
|
||||
return pwhash.check(password, this.salt, this.hash);
|
||||
}
|
||||
}
|
||||
async function getById(_id) {
|
||||
let udoc = await coll.findOne({ _id });
|
||||
const udoc = await coll.findOne({ _id });
|
||||
if (!udoc) throw new UserNotFoundError(_id);
|
||||
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
|
||||
const role = await collRole.findOne({ _id: udoc.role || 'default' });
|
||||
udoc.perm = role.perm;
|
||||
return new USER(udoc);
|
||||
}
|
||||
async function getList(uids) {
|
||||
let r = {};
|
||||
for (let uid of uids)
|
||||
r[uid] = await getById(uid);
|
||||
const r = {};
|
||||
for (const uid of uids) r[uid] = await getById(uid); // eslint-disable-line no-await-in-loop
|
||||
return r;
|
||||
}
|
||||
async function getByUname(uname) {
|
||||
let unameLower = uname.trim().toLowerCase();
|
||||
let udoc = await coll.findOne({ unameLower });
|
||||
const unameLower = uname.trim().toLowerCase();
|
||||
const udoc = await coll.findOne({ unameLower });
|
||||
if (!udoc) throw new UserNotFoundError(uname);
|
||||
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
|
||||
const role = await collRole.findOne({ _id: udoc.role || 'default' });
|
||||
udoc.perm = role.perm;
|
||||
return new USER(udoc);
|
||||
}
|
||||
async function getByEmail(mail, ignoreMissing = false) {
|
||||
let mailLower = mail.trim().toLowerCase();
|
||||
let udoc = await coll.findOne({ mailLower });
|
||||
const mailLower = mail.trim().toLowerCase();
|
||||
const udoc = await coll.findOne({ mailLower });
|
||||
if (!udoc) {
|
||||
if (ignoreMissing) return null;
|
||||
else throw new UserNotFoundError(mail);
|
||||
throw new UserNotFoundError(mail);
|
||||
}
|
||||
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
|
||||
const role = await collRole.findOne({ _id: udoc.role || 'default' });
|
||||
udoc.perm = role.perm;
|
||||
return new USER(udoc);
|
||||
}
|
||||
async function setPassword(uid, password) {
|
||||
function setPassword(uid, password) {
|
||||
validator.checkPassword(password);
|
||||
let salt = pwhash.salt();
|
||||
return await coll.findOneAndUpdate({ _id: uid }, {
|
||||
$set: { salt, hash: pwhash.hash(password, salt) }
|
||||
const salt = pwhash.salt();
|
||||
return coll.findOneAndUpdate({ _id: uid }, {
|
||||
$set: { salt, hash: pwhash.hash(password, salt) },
|
||||
});
|
||||
}
|
||||
async function setEmail(uid, mail) {
|
||||
validator.checkEmail(mail);
|
||||
return await setById(uid, { mail, mailLower: mail.trim().toLowerCase() });
|
||||
}
|
||||
function setById(uid, args) {
|
||||
coll.findOneAndUpdate({ _id: uid }, { $set: args });
|
||||
}
|
||||
function setEmail(uid, mail) {
|
||||
validator.checkEmail(mail);
|
||||
return setById(uid, { mail, mailLower: mail.trim().toLowerCase() });
|
||||
}
|
||||
async function changePassword(uid, currentPassword, newPassword) {
|
||||
validator.checkPassword(newPassword);
|
||||
let udoc = await getById(uid);
|
||||
const udoc = await getById(uid);
|
||||
udoc.checkPassword(currentPassword);
|
||||
let salt = pwhash.salt();
|
||||
const salt = pwhash.salt();
|
||||
return await coll.findOneAndUpdate({
|
||||
_id: udoc._id
|
||||
_id: udoc._id,
|
||||
}, {
|
||||
$set: { salt, hash: pwhash.hash(newPassword, salt) }
|
||||
$set: { salt, hash: pwhash.hash(newPassword, salt) },
|
||||
});
|
||||
}
|
||||
async function create({ uid, mail, uname, password, regip = '127.0.0.1', role = 'default' }) {
|
||||
async function create({
|
||||
uid, mail, uname, password, regip = '127.0.0.1', role = 'default',
|
||||
}) {
|
||||
validator.checkUname(uname);
|
||||
validator.checkPassword(password);
|
||||
validator.checkEmail(mail);
|
||||
let salt = pwhash.salt();
|
||||
const salt = pwhash.salt();
|
||||
if (!uid) uid = system.incUserCounter();
|
||||
try {
|
||||
await coll.insertOne({
|
||||
@@ -108,10 +111,10 @@ async function create({ uid, mail, uname, password, regip = '127.0.0.1', role =
|
||||
loginat: new Date(),
|
||||
loginip: regip,
|
||||
role,
|
||||
gravatar: mail
|
||||
gravatar: mail,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new UserAlreadyExistError([uid, uname, email]);
|
||||
throw new UserAlreadyExistError([uid, uname, mail]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +123,14 @@ function getMany(params) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
changePassword, create, getByEmail,
|
||||
getById, getByUname, getMany,
|
||||
setById, setEmail,
|
||||
setPassword, getList
|
||||
};
|
||||
changePassword,
|
||||
create,
|
||||
getByEmail,
|
||||
getById,
|
||||
getByUname,
|
||||
getMany,
|
||||
setById,
|
||||
setEmail,
|
||||
setPassword,
|
||||
getList,
|
||||
};
|
||||
+37
-38
@@ -4,77 +4,76 @@ module.exports = {
|
||||
showScoreboard: () => true,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
stat: (tdoc, journal) => {
|
||||
let naccept = {}, effective = {}, detail = [], accept = 0, time = 0;
|
||||
for (let j in journal) {
|
||||
if (tdoc.pids.includes(j.pid) && !(effective.includes(j.pid) && effective[j.pid].accept))
|
||||
effective[j.pid] = j;
|
||||
if (!j.accept)
|
||||
naccept[j.pid]++;
|
||||
const naccept = {}; const effective = {}; const detail = []; let accept = 0; let time = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid) && !(effective.includes(j.pid) && effective[j.pid].accept)) { effective[j.pid] = j; }
|
||||
if (!j.accept) { naccept[j.pid]++; }
|
||||
}
|
||||
function _time(jdoc) {
|
||||
let real = jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
let penalty = 20 * 60 * naccept[jdoc.pid];
|
||||
const real = jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
const penalty = 20 * 60 * naccept[jdoc.pid];
|
||||
return real + penalty;
|
||||
}
|
||||
for (let j of effective)
|
||||
detail.push(Object.assign({}, j, {
|
||||
naccept: naccept[j.pid], time: _time(j)
|
||||
}));
|
||||
for (let d of detail) {
|
||||
for (const j of effective) { detail.push({ ...j, naccept: naccept[j.pid], time: _time(j) }); }
|
||||
for (const d of detail) {
|
||||
accept += d.accept;
|
||||
if (d.accept) time += d.time;
|
||||
}
|
||||
return { accept, time, detail };
|
||||
},
|
||||
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, dudict, pdict) {
|
||||
let columns = [
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'display_name', value: _('Display Name') },
|
||||
{ type: 'solved_problems', value: _('Solved Problems') }
|
||||
{ type: 'solved_problems', value: _('Solved Problems') },
|
||||
];
|
||||
if (is_export) {
|
||||
columns.push({ 'type': 'total_time', 'value': _('Total Time (Seconds)') });
|
||||
columns.push({ 'type': 'total_time_str', 'value': _('Total Time') });
|
||||
columns.push({ type: 'total_time', value: _('Total Time (Seconds)') });
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
}
|
||||
for (let i in tdoc.pids)
|
||||
for (const i in tdoc.pids) {
|
||||
if (is_export) {
|
||||
columns.push({
|
||||
type: 'problem_flag',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title)
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time (Seconds)'))
|
||||
value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time_str',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time'))
|
||||
value: '#{0} {1}'.format(i + 1, _('Time')),
|
||||
});
|
||||
} else
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1), raw: pdict[tdoc.pids[i]]
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
let rows = [columns];
|
||||
for (let [rank, tsdoc] of ranked_tsdocs) {
|
||||
let tsddict = {};
|
||||
if (tdoc.detail)
|
||||
for (let item of tsdoc.detail)
|
||||
tsddict[item.pid] = item;
|
||||
let row = [];
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of ranked_tsdocs) {
|
||||
const tsddict = {};
|
||||
if (tdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push(
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'display_name', value: (dudict[tsdoc.uid] || {}).display_name || '' },
|
||||
{ type: 'string', value: tsdoc.accept || 0 });
|
||||
if (is_export)
|
||||
{ type: 'string', value: tsdoc.accept || 0 },
|
||||
);
|
||||
if (is_export) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 }
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
for (let pid of tdoc.pids) {
|
||||
let rdoc, col_accepted, col_time, col_time_str;
|
||||
}
|
||||
for (const pid of tdoc.pids) {
|
||||
let rdoc; let col_accepted; let col_time; let
|
||||
col_time_str;
|
||||
if ((tsddict[pid] || {}).accept) {
|
||||
rdoc = tsddict[pid].rid;
|
||||
col_accepted = _('Accepted');
|
||||
@@ -94,12 +93,12 @@ module.exports = {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0}\n{1}'.format(col_accepted, col_time_str),
|
||||
raw: rdoc
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
const { ValidationError } = require('../../error');
|
||||
|
||||
module.exports = {
|
||||
TEXT: 'Assignment',
|
||||
check(data) {
|
||||
@@ -7,88 +8,94 @@ module.exports = {
|
||||
if (data.penalty_since > data.end_at) throw new ValidationError('penalty_since', 'end_at');
|
||||
},
|
||||
stat(tdoc, journal) {
|
||||
let effective = {}, detail = [], score = 0, penalty_score = 0, time = 0;
|
||||
for (let j in journal)
|
||||
if (tdoc.pids.includes(j.pid) && !(effective.includes(j.pid) && effective[j.pid].accept))
|
||||
effective[j.pid] = j;
|
||||
const _time = jdoc => jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
const effective = {};
|
||||
const detail = [];
|
||||
let score = 0;
|
||||
let penalty_score = 0;
|
||||
let time = 0;
|
||||
for (const j in journal) { if (tdoc.pids.includes(j.pid) && !(effective.includes(j.pid) && effective[j.pid].accept)) effective[j.pid] = j; }
|
||||
const _time = (jdoc) => jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
function _penalty_score(jdoc) {
|
||||
let exceed_seconds = jdoc.rid.generationTime - Math.floor(tdoc.penalty_since / 1000);
|
||||
const exceed_seconds = jdoc.rid.generationTime - Math.floor(tdoc.penalty_since / 1000);
|
||||
if (exceed_seconds < 0) return jdoc.score;
|
||||
else return 0.5 * jdoc.score;
|
||||
return 0.5 * jdoc.score;
|
||||
}
|
||||
for (let j in effective) {
|
||||
detail.push(Object.assign({}, effective[j], {
|
||||
penalty_score: _penalty_score(effective[j]), time: _time(effective[j])
|
||||
}));
|
||||
for (const j in effective) {
|
||||
detail.push({ ...effective[j], penalty_score: _penalty_score(effective[j]), time: _time(effective[j]) });
|
||||
}
|
||||
for (let d of detail) {
|
||||
for (const d of detail) {
|
||||
score += d.score;
|
||||
penalty_score += d.penalty_score;
|
||||
time += d.time;
|
||||
}
|
||||
return { score, penalty_score, time, detail };
|
||||
return {
|
||||
score, penalty_score, time, detail,
|
||||
};
|
||||
},
|
||||
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, dudict, pdict) {
|
||||
let columns = [
|
||||
{ 'type': 'rank', 'value': _('Rank') },
|
||||
{ 'type': 'user', 'value': _('User') },
|
||||
{ 'type': 'display_name', 'value': _('Display Name') },
|
||||
{ 'type': 'total_score', 'value': _('Score') }
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'display_name', value: _('Display Name') },
|
||||
{ type: 'total_score', value: _('Score') },
|
||||
];
|
||||
if (is_export)
|
||||
if (is_export) {
|
||||
columns.push(
|
||||
{ type: 'total_original_score', value: _('Original Score') },
|
||||
{ type: 'total_time', value: _('Total Time (Seconds)') }
|
||||
{ type: 'total_time', value: _('Total Time (Seconds)') },
|
||||
);
|
||||
}
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
for (let i in tdoc.pids)
|
||||
if (is_export)
|
||||
for (const i in tdoc.pids) {
|
||||
if (is_export) {
|
||||
columns.push(
|
||||
{ type: 'problem_score', value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title) },
|
||||
{ type: 'problem_original_score', value: '#{0} {1}'.format(i + 1, _('Original Score')) },
|
||||
{ type: 'problem_time', value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')) },
|
||||
{ type: 'problem_time_str', value: '#{0} {1}'.format(i + 1, _('Time')) }
|
||||
{ type: 'problem_time_str', value: '#{0} {1}'.format(i + 1, _('Time')) },
|
||||
);
|
||||
else columns.push({ type: 'problem_detail', value: '#{0}'.format(i + 1), 'raw': pdict[tdoc.pids[i]] });
|
||||
let rows = [columns];
|
||||
for (let [rank, tsdoc] in ranked_tsdocs) {
|
||||
let tsddict = {};
|
||||
if (tsdoc.detail)
|
||||
for (let item of tsdoc.detail)
|
||||
tsddict[item.pid] = item;
|
||||
let row = [
|
||||
} else columns.push({ type: 'problem_detail', value: '#{0}'.format(i + 1), raw: pdict[tdoc.pids[i]] });
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] in ranked_tsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'display_name', value: (dudict[tsdoc.uid] || {}).display_name || '' },
|
||||
{ type: 'string', value: tsdoc.penalty_score || 0 }
|
||||
{ type: 'string', value: tsdoc.penalty_score || 0 },
|
||||
];
|
||||
if (is_export)
|
||||
if (is_export) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.score || 0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 }
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
}
|
||||
row.push({ type: 'string', value: tsdoc.time || 0 });
|
||||
for (let pid of tdoc.pids) {
|
||||
let rdoc = (tsddict[pid] || {}).rid || null;
|
||||
let col_score = (tsddict[pid] || {}).penalty_score || '-';
|
||||
let col_original_score = (tsddict[pid] || {}).score || '-';
|
||||
let col_time = (tsddict[pid] || {}).time || '-';
|
||||
let col_time_str = col_time != '-' ? col_time : '-';
|
||||
if (is_export) row.push(
|
||||
{ type: 'string', value: col_score },
|
||||
{ type: 'string', value: col_original_score },
|
||||
{ type: 'string', value: col_time },
|
||||
{ type: 'string', value: col_time_str },
|
||||
);
|
||||
else row.push({
|
||||
type: 'record',
|
||||
value: '{0} / {1}\n{2}'.format(col_score, col_original_score, col_time_str),
|
||||
raw: rdoc
|
||||
});
|
||||
for (const pid of tdoc.pids) {
|
||||
const rdoc = (tsddict[pid] || {}).rid || null;
|
||||
const col_score = (tsddict[pid] || {}).penalty_score || '-';
|
||||
const col_original_score = (tsddict[pid] || {}).score || '-';
|
||||
const col_time = (tsddict[pid] || {}).time || '-';
|
||||
const col_time_str = col_time != '-' ? col_time : '-';
|
||||
if (is_export) {
|
||||
row.push(
|
||||
{ type: 'string', value: col_score },
|
||||
{ type: 'string', value: col_original_score },
|
||||
{ type: 'string', value: col_time },
|
||||
{ type: 'string', value: col_time_str },
|
||||
);
|
||||
} else {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0} / {1}\n{2}'.format(col_score, col_original_score, col_time_str),
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
+24
-21
@@ -4,12 +4,14 @@ module.exports = {
|
||||
TEXT: 'OI',
|
||||
check: () => { },
|
||||
stat: (tdoc, journal) => {
|
||||
let detail = {}, score = 0;
|
||||
for (let j in journal)
|
||||
const detail = {};
|
||||
let score = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)) {
|
||||
detail[j.pid] = j;
|
||||
score += j.score;
|
||||
}
|
||||
}
|
||||
return { score, detail };
|
||||
},
|
||||
showScoreboard(tdoc, now) {
|
||||
@@ -18,43 +20,44 @@ module.exports = {
|
||||
showRecord(tdoc, now) {
|
||||
return now > tdoc.endAt;
|
||||
},
|
||||
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, pdict) {
|
||||
let columns = [
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'total_score', value: _('Total Score') }
|
||||
{ type: 'total_score', value: _('Total Score') },
|
||||
];
|
||||
for (let i in tdoc.pids)
|
||||
if (is_export)
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push({
|
||||
type: 'problem_score',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title)
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
else
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]]
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
let rows = [columns];
|
||||
for (let [rank, tsdoc] of ranked_tsdocs) {
|
||||
let tsddict = {};
|
||||
if (tsdoc.journal)
|
||||
for (let item of tsdoc.journal)
|
||||
tsddict[item.pid] = item;
|
||||
let row = [];
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.journal) { for (const item of tsdoc.journal) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push({ type: 'string', value: rank });
|
||||
row.push({ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] });
|
||||
row.push({ type: 'string', value: tsdoc.score || 0 });
|
||||
for (let pid of tdoc.pids)
|
||||
for (const pid of tdoc.pids) {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: (tsddict[pid] || {}).score || '-',
|
||||
raw: (tsddict[pid] || {}).rid || null
|
||||
raw: (tsddict[pid] || {}).rid || null,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
rank: tdocs => ranked(tdocs, (a, b) => a.score == b.score)
|
||||
};
|
||||
rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score),
|
||||
};
|
||||
+13
-14
@@ -1,9 +1,8 @@
|
||||
const
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
path = require('path'),
|
||||
yaml = require('js-yaml'),
|
||||
{ defaultsDeep } = require('lodash');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
const { defaultsDeep } = require('lodash');
|
||||
|
||||
let options = {
|
||||
db: {
|
||||
@@ -11,16 +10,16 @@ let options = {
|
||||
host: '127.0.0.1',
|
||||
port: 27017,
|
||||
username: '',
|
||||
password: ''
|
||||
password: '',
|
||||
},
|
||||
template: {
|
||||
path: path.join(__dirname, '..', 'templates')
|
||||
path: path.join(__dirname, '..', 'templates'),
|
||||
},
|
||||
listen: {
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
domain: '',
|
||||
https: false
|
||||
https: false,
|
||||
},
|
||||
smtp: {
|
||||
from: '',
|
||||
@@ -28,7 +27,7 @@ let options = {
|
||||
port: 465,
|
||||
user: '',
|
||||
pass: '',
|
||||
secure: false
|
||||
secure: false,
|
||||
},
|
||||
session: {
|
||||
keys: ['Hydro'],
|
||||
@@ -36,21 +35,21 @@ let options = {
|
||||
domain: '127.0.0.1',
|
||||
saved_expire_seconds: 3600 * 24,
|
||||
unsaved_expire_seconds: 600,
|
||||
registration_token_expire_seconds: 600
|
||||
registration_token_expire_seconds: 600,
|
||||
},
|
||||
constants: {
|
||||
PROBLEM_PER_PAGE: 100,
|
||||
RECORD_PER_PAGE: 100,
|
||||
SOLUTION_PER_PAGE: 20,
|
||||
CONTEST_PER_PAGE: 20
|
||||
}
|
||||
CONTEST_PER_PAGE: 20,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
let f = path.resolve(process.cwd(), 'config.yaml');
|
||||
if (!fs.existsSync(f)) f = path.resolve(os.homedir(), '.config', 'hydro', 'config.yaml');
|
||||
if (!fs.existsSync(f)) f = path.resolve('/config/config.yaml');
|
||||
let t = yaml.safeLoad(fs.readFileSync(f));
|
||||
const t = yaml.safeLoad(fs.readFileSync(f));
|
||||
options = defaultsDeep(t, options);
|
||||
} catch (e) {
|
||||
console.error('Cannot load config');
|
||||
|
||||
+25
-27
@@ -1,4 +1,4 @@
|
||||
let permissions = {
|
||||
const permissions = {
|
||||
PERM_NONE: '0',
|
||||
PERM_ALL: '-',
|
||||
PERM_LOGGEDIN: '~',
|
||||
@@ -41,32 +41,30 @@ let permissions = {
|
||||
PERM_VIEW_TRAINING: 'a',
|
||||
PERM_CREATE_TRAINING: 'b',
|
||||
PERM_EDIT_TRAINING: 'c',
|
||||
PERM_JUDGE: 'd'
|
||||
PERM_JUDGE: 'd',
|
||||
};
|
||||
permissions.PERM_BASIC =
|
||||
permissions.PERM_VIEW +
|
||||
permissions.PERM_VIEW_PROBLEM +
|
||||
permissions.PERM_VIEW_PROBLEM_SOLUTION +
|
||||
permissions.PERM_VIEW_DISCUSSION +
|
||||
permissions.PERM_VIEW_CONTEST +
|
||||
permissions.PERM_VIEW_CONTEST_SCOREBOARD +
|
||||
permissions.PERM_REGISTER_USER;
|
||||
permissions.PERM_DEFAULT =
|
||||
permissions.PERM_VIEW +
|
||||
permissions.PERM_VIEW_PROBLEM +
|
||||
permissions.PERM_SUBMIT_PROBLEM +
|
||||
permissions.PERM_VIEW_PROBLEM_SOLUTION +
|
||||
permissions.PERM_CREATE_PROBLEM_SOLUTION +
|
||||
permissions.PERM_VOTE_PROBLEM_SOLUTION +
|
||||
permissions.PERM_REPLY_PROBLEM_SOLUTION +
|
||||
permissions.PERM_VIEW_DISCUSSION +
|
||||
permissions.PERM_CREATE_DISCUSSION +
|
||||
permissions.PERM_REPLY_DISCUSSION +
|
||||
permissions.PERM_VIEW_CONTEST +
|
||||
permissions.PERM_VIEW_CONTEST_SCOREBOARD +
|
||||
permissions.PERM_ATTEND_CONTEST +
|
||||
permissions.PERM_VIEW_TRAINING +
|
||||
permissions.PERM_CREATE_TRAINING +
|
||||
permissions.PERM_LOGGEDIN;
|
||||
permissions.PERM_BASIC = permissions.PERM_VIEW
|
||||
+ permissions.PERM_VIEW_PROBLEM
|
||||
+ permissions.PERM_VIEW_PROBLEM_SOLUTION
|
||||
+ permissions.PERM_VIEW_DISCUSSION
|
||||
+ permissions.PERM_VIEW_CONTEST
|
||||
+ permissions.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
+ permissions.PERM_REGISTER_USER;
|
||||
permissions.PERM_DEFAULT = permissions.PERM_VIEW
|
||||
+ permissions.PERM_VIEW_PROBLEM
|
||||
+ permissions.PERM_SUBMIT_PROBLEM
|
||||
+ permissions.PERM_VIEW_PROBLEM_SOLUTION
|
||||
+ permissions.PERM_CREATE_PROBLEM_SOLUTION
|
||||
+ permissions.PERM_VOTE_PROBLEM_SOLUTION
|
||||
+ permissions.PERM_REPLY_PROBLEM_SOLUTION
|
||||
+ permissions.PERM_VIEW_DISCUSSION
|
||||
+ permissions.PERM_CREATE_DISCUSSION
|
||||
+ permissions.PERM_REPLY_DISCUSSION
|
||||
+ permissions.PERM_VIEW_CONTEST
|
||||
+ permissions.PERM_VIEW_CONTEST_SCOREBOARD
|
||||
+ permissions.PERM_ATTEND_CONTEST
|
||||
+ permissions.PERM_VIEW_TRAINING
|
||||
+ permissions.PERM_CREATE_TRAINING
|
||||
+ permissions.PERM_LOGGEDIN;
|
||||
permissions.PERM_ADMIN = permissions.PERM_ALL;
|
||||
module.exports = permissions;
|
||||
@@ -1,15 +1,14 @@
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
const bus = new EventEmitter();
|
||||
module.exports = {
|
||||
subscribe(events, handler) {
|
||||
for (let event of events)
|
||||
bus.on(event, handler);
|
||||
for (const event of events) bus.on(event, handler);
|
||||
},
|
||||
unsubscribe(events, handler) {
|
||||
for (let event of events)
|
||||
bus.off(event, handler);
|
||||
for (const event of events) bus.off(event, handler);
|
||||
},
|
||||
publish(event, data) {
|
||||
bus.emit(event, { value: data, event });
|
||||
}
|
||||
},
|
||||
};
|
||||
+4
-3
@@ -1,19 +1,20 @@
|
||||
const Mongo = require('mongodb');
|
||||
const options = require('../options');
|
||||
|
||||
let mongourl = 'mongodb://';
|
||||
if (options.db.username) mongourl += options.db.username + ':' + options.db.password + '@';
|
||||
if (options.db.username) mongourl += `${options.db.username}:${options.db.password}@`;
|
||||
mongourl += `${options.db.host}:${options.db.port}/${options.db.name}`;
|
||||
/**
|
||||
* @type {import('mongodb').Db}
|
||||
*/
|
||||
let db = null;
|
||||
Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(Database => {
|
||||
.then((Database) => {
|
||||
db = Database.db(options.db.name);
|
||||
db.Db = Database;
|
||||
global.bus.emit('connected');
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
collection: c => db.collection(c)
|
||||
collection: (c) => db.collection(c),
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
const
|
||||
{ GridFSBucket } = require('mongodb'),
|
||||
db = require('./db');
|
||||
const { GridFSBucket } = require('mongodb');
|
||||
const db = require('./db');
|
||||
|
||||
if (!db.s) db.s = {};
|
||||
module.exports = new GridFSBucket(db);
|
||||
module.exports = new GridFSBucket(db);
|
||||
+12
-13
@@ -1,6 +1,6 @@
|
||||
let q = {};
|
||||
const q = {};
|
||||
module.exports = {
|
||||
async connect() { },
|
||||
async connect() { }, // eslint-disable-line no-empty-function
|
||||
async assert(queue) {
|
||||
q[queue] = q[queue] || [];
|
||||
},
|
||||
@@ -9,20 +9,19 @@ module.exports = {
|
||||
},
|
||||
async get(queue, wait = true) {
|
||||
if (wait) {
|
||||
while (!q[queue].length)
|
||||
await new Promise(resolve => {
|
||||
while (!q[queue].length) {
|
||||
await new Promise((resolve) => { // eslint-disable-line no-await-in-loop
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
let data = q[queue][0];
|
||||
}
|
||||
const data = q[queue][0];
|
||||
q[queue].splice(0, 1);
|
||||
return data;
|
||||
} else {
|
||||
if (!q[queue].length) return null;
|
||||
else {
|
||||
let data = q[queue][0];
|
||||
q[queue].splice(0, 1);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!q[queue].length) return null;
|
||||
|
||||
const data = q[queue][0];
|
||||
q[queue].splice(0, 1);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
+141
-101
@@ -1,41 +1,42 @@
|
||||
const
|
||||
path = require('path'),
|
||||
{ ObjectID } = require('bson'),
|
||||
{ MongoError } = require('mongodb'),
|
||||
Koa = require('koa'),
|
||||
morgan = require('koa-morgan'),
|
||||
Body = require('koa-body'),
|
||||
Router = require('koa-router'),
|
||||
cache = require('koa-static-cache'),
|
||||
sockjs = require('sockjs'),
|
||||
validator = require('../lib/validator'),
|
||||
template = require('../lib/template'),
|
||||
user = require('../model/user'),
|
||||
blacklist = require('../model/blacklist'),
|
||||
token = require('../model/token'),
|
||||
opcount = require('../model/opcount'),
|
||||
{ UserNotFoundError, BlacklistedError, PermissionError,
|
||||
NotFoundError, UserFacingError } = require('../error');
|
||||
|
||||
const path = require('path');
|
||||
const { ObjectID } = require('bson');
|
||||
const Koa = require('koa');
|
||||
const morgan = require('koa-morgan');
|
||||
const Body = require('koa-body');
|
||||
const Router = require('koa-router');
|
||||
const cache = require('koa-static-cache');
|
||||
const sockjs = require('sockjs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const options = require('../options');
|
||||
let http = options.listen.https ? require('https') : require('http');
|
||||
let app = new Koa();
|
||||
let server = http.createServer(app.callback());
|
||||
const validator = require('../lib/validator');
|
||||
const template = require('../lib/template');
|
||||
const user = require('../model/user');
|
||||
const blacklist = require('../model/blacklist');
|
||||
const token = require('../model/token');
|
||||
const opcount = require('../model/opcount');
|
||||
const {
|
||||
UserNotFoundError, BlacklistedError, PermissionError,
|
||||
NotFoundError, UserFacingError,
|
||||
} = require('../error');
|
||||
|
||||
const app = new Koa();
|
||||
const server = (options.listen.https ? https : http).createServer(app.callback());
|
||||
app.keys = options.session.keys;
|
||||
app.use(cache(path.join(process.cwd(), '.uibuild'), {
|
||||
maxAge: 365 * 24 * 60 * 60
|
||||
maxAge: 365 * 24 * 60 * 60,
|
||||
}));
|
||||
app.use(Body({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
maxFileSize: 256 * 1024 * 1024
|
||||
}
|
||||
maxFileSize: 256 * 1024 * 1024,
|
||||
},
|
||||
}));
|
||||
let router = new Router();
|
||||
const router = new Router();
|
||||
|
||||
class Handler {
|
||||
/**
|
||||
* @param {import('koa').Context} ctx
|
||||
* @param {import('koa').Context} ctx
|
||||
*/
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
@@ -46,7 +47,7 @@ class Handler {
|
||||
body: ctx.request.body,
|
||||
files: ctx.request.files,
|
||||
query: ctx.query,
|
||||
path: ctx.path
|
||||
path: ctx.path,
|
||||
};
|
||||
this.response = {
|
||||
body: '',
|
||||
@@ -54,23 +55,24 @@ class Handler {
|
||||
status: null,
|
||||
template: null,
|
||||
redirect: null,
|
||||
attachment: name => ctx.attachment(name)
|
||||
attachment: (name) => ctx.attachment(name),
|
||||
};
|
||||
this.UIContext = {
|
||||
cdn_prefix: '/',
|
||||
url_prefix: '/'
|
||||
url_prefix: '/',
|
||||
};
|
||||
this._handler = {};
|
||||
this.session = {};
|
||||
}
|
||||
|
||||
renderHTML(name, context) {
|
||||
console.time(name);
|
||||
this.hasPerm = perm => this.user.hasPerm(perm);
|
||||
this.hasPerm = (perm) => this.user.hasPerm(perm);
|
||||
return new Promise((resolve, reject) => {
|
||||
template.render(name, Object.assign(context, {
|
||||
handler: this,
|
||||
_: str => str ? str.toString().translate(this.user.language) : '',
|
||||
user: this.user
|
||||
_: (str) => (str ? str.toString().translate(this.user.language) : ''),
|
||||
user: this.user,
|
||||
}), (error, res) => {
|
||||
console.timeEnd(name);
|
||||
if (error) reject(error);
|
||||
@@ -78,35 +80,41 @@ class Handler {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async render(name, context) {
|
||||
this.response.body = await this.renderHTML(name, context);
|
||||
this.response.type = 'text/html';
|
||||
}
|
||||
render_title(str) {
|
||||
|
||||
renderTitle(str) { // eslint-disable-line class-methods-use-this
|
||||
return str;
|
||||
}
|
||||
checkPerm(perm) {
|
||||
for (let i in arguments) {
|
||||
if (arguments[i] instanceof Array) {
|
||||
|
||||
checkPerm(...args) {
|
||||
for (const i in args) {
|
||||
if (args[i] instanceof Array) {
|
||||
let p = false;
|
||||
for (let j in arguments)
|
||||
if (this.user.hasPerm(arguments[i][j])) {
|
||||
for (const j in args) {
|
||||
if (this.user.hasPerm(args[i][j])) {
|
||||
p = true;
|
||||
break;
|
||||
}
|
||||
if (!p) throw new PermissionError([arguments[i]]);
|
||||
} else {
|
||||
if (this.user.hasPerm(arguments[i])) continue;
|
||||
else throw new PermissionError([[arguments[i]]]);
|
||||
}
|
||||
if (!p) throw new PermissionError([args[i]]);
|
||||
} else if (!this.user.hasPerm(args[i])) {
|
||||
throw new PermissionError([[args[i]]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
async limitRate(op, period_secs, max_operations) {
|
||||
await opcount.inc(op, this.request.ip, period_secs, max_operations);
|
||||
|
||||
async limitRate(op, periodSecs, maxOperations) {
|
||||
await opcount.inc(op, this.request.ip, periodSecs, maxOperations);
|
||||
}
|
||||
|
||||
back() {
|
||||
this.response.redirect = this.request.headers.referer || '/';
|
||||
}
|
||||
|
||||
async ___prepare() {
|
||||
this.now = new Date();
|
||||
this._handler.sid = this.request.cookies.get('sid');
|
||||
@@ -114,19 +122,25 @@ class Handler {
|
||||
this._handler.tokenType = token.TYPE_SESSION;
|
||||
if (this._handler.save) this._handler.expireSeconds = options.session.saved_expire_seconds;
|
||||
else this._handler.expireSeconds = options.session.unsaved_expire_seconds;
|
||||
this.session = this._handler.sid ?
|
||||
await token.update(this._handler.sid, this._handler.tokenType, this._handler.expireSeconds, {
|
||||
update_ip: this.request.ip,
|
||||
update_ua: this.request.headers['user-agent'] || ''
|
||||
}) : { uid: 1 };
|
||||
this.session = this._handler.sid
|
||||
? await token.update(
|
||||
this._handler.sid,
|
||||
this._handler.tokenType,
|
||||
this._handler.expireSeconds,
|
||||
{
|
||||
update_ip: this.request.ip,
|
||||
update_ua: this.request.headers['user-agent'] || '',
|
||||
},
|
||||
) : { uid: 1 };
|
||||
if (!this.session) this.session = { uid: 1 };
|
||||
let bdoc = await blacklist.get(this.request.ip);
|
||||
const bdoc = await blacklist.get(this.request.ip);
|
||||
if (bdoc) throw new BlacklistedError(this.request.ip);
|
||||
this.user = await user.getById(this.session.uid);
|
||||
if (!this.user) throw new UserNotFoundError(this.session.uid);
|
||||
this.csrf_token = (await token.add(token.TYPE_CSRF_TOKEN, 600, { path: this.request.path }))[0];
|
||||
this.preferJson = (this.request.headers['accept'] || '').includes('application/json');
|
||||
[this.csrfToken] = await token.add(token.TYPE_CSRF_TOKEN, 600, { path: this.request.path });
|
||||
this.preferJson = (this.request.headers.accept || '').includes('application/json');
|
||||
}
|
||||
|
||||
async ___cleanup() {
|
||||
try {
|
||||
await this.renderBody();
|
||||
@@ -137,20 +151,24 @@ class Handler {
|
||||
await this.putResponse();
|
||||
await this.saveCookie();
|
||||
}
|
||||
|
||||
async renderBody() {
|
||||
if (!(this.response.body || this.response.template)) {
|
||||
this.response.body = { error: new NotFoundError() };
|
||||
this.response.template = 'error.html';
|
||||
}
|
||||
if (!this.preferJson)
|
||||
if (!this.preferJson) {
|
||||
if (this.response.body || this.response.template) {
|
||||
if (this.request.query.noTemplate || this.preferJson) return;
|
||||
if (this.request.query.template || this.response.template) {
|
||||
const templateName = this.request.query.template || this.response.template;
|
||||
if (templateName) {
|
||||
this.response.body = this.response.body || {};
|
||||
await this.render(this.request.query.template || this.response.template, this.response.body);
|
||||
await this.render(templateName, this.response.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async putResponse() {
|
||||
if (this.response.redirect && !this.preferJson) {
|
||||
this.ctx.response.type = 'application/octet-stream';
|
||||
@@ -164,27 +182,40 @@ class Handler {
|
||||
if (this.response.type) this.ctx.response.type = this.response.type;
|
||||
}
|
||||
}
|
||||
|
||||
async saveCookie() {
|
||||
if (this.session.sid)
|
||||
await token.update(this.session.sid, this._handler.tokenType, this._handler.expireSeconds, {
|
||||
updateIp: this.request.ip,
|
||||
updateUa: this.request.headers['user-agent'] || ''
|
||||
});
|
||||
else
|
||||
[this.session.sid] = await token.add(this._handler.tokenType, this._handler.expireSeconds, {
|
||||
createIp: this.request.ip,
|
||||
createUa: this.request.headers['user-agent'] || '',
|
||||
updateIp: this.request.ip,
|
||||
updateUa: this.request.headers['user-agent'] || '',
|
||||
...this.session
|
||||
});
|
||||
let cookie = { secure: options.session.secure };
|
||||
if (this.session.sid) {
|
||||
await token.update(
|
||||
this.session.sid,
|
||||
this._handler.tokenType,
|
||||
this._handler.expireSeconds,
|
||||
{
|
||||
updateIp: this.request.ip,
|
||||
updateUa: this.request.headers['user-agent'] || '',
|
||||
},
|
||||
);
|
||||
} else {
|
||||
[this.session.sid] = await token.add(
|
||||
this._handler.tokenType,
|
||||
this._handler.expireSeconds,
|
||||
{
|
||||
createIp: this.request.ip,
|
||||
createUa: this.request.headers['user-agent'] || '',
|
||||
updateIp: this.request.ip,
|
||||
updateUa: this.request.headers['user-agent'] || '',
|
||||
...this.session,
|
||||
},
|
||||
);
|
||||
}
|
||||
const cookie = { secure: options.session.secure };
|
||||
if (this._handler.save) {
|
||||
cookie.expires = this.session.expireAt, cookie.maxAge = this._handler.expireSeconds;
|
||||
cookie.expires = this.session.expireAt;
|
||||
cookie.maxAge = this._handler.expireSeconds;
|
||||
this.request.cookies.set('save', 'true', cookie);
|
||||
}
|
||||
this.ctx.cookies.set('sid', this.session.sid, cookie);
|
||||
}
|
||||
|
||||
async onerror(error) {
|
||||
console.error(error.message, error.params);
|
||||
console.error(error.stack);
|
||||
@@ -193,12 +224,12 @@ class Handler {
|
||||
await this.___cleanup().catch(() => { });
|
||||
}
|
||||
}
|
||||
function Route(route, handler) {
|
||||
function Route(route, RouteHandler) {
|
||||
router.all(route, async (ctx) => {
|
||||
let h = new handler(ctx);
|
||||
const h = new RouteHandler(ctx);
|
||||
try {
|
||||
let method = ctx.method.toLowerCase();
|
||||
let args = Object.assign({}, ctx.params, ctx.query, ctx.request.body);
|
||||
const method = ctx.method.toLowerCase();
|
||||
const args = { ...ctx.params, ...ctx.query, ...ctx.request.body };
|
||||
|
||||
if (args.content) validator.checkContent(args.content);
|
||||
if (args.title) validator.checkContent(args.title);
|
||||
@@ -207,7 +238,7 @@ function Route(route, handler) {
|
||||
if (args.rid) args.rid = new ObjectID(args.rid);
|
||||
if (args.tid) args.tid = new ObjectID(args.tid);
|
||||
if (args.duration) args.duration = parseFloat(args.duration);
|
||||
if (args.pids) args.pids = args.pids.split(',').map(i => i.trim());
|
||||
if (args.pids) args.pids = args.pids.split(',').map((i) => i.trim());
|
||||
|
||||
if (h.___prepare) await h.___prepare(args);
|
||||
if (h.__prepare) await h.__prepare(args);
|
||||
@@ -219,9 +250,10 @@ function Route(route, handler) {
|
||||
if (h[`_${method}`]) await h[`_${method}`](args);
|
||||
if (h[method]) await h[method](args);
|
||||
|
||||
if (method == 'post' && ctx.request.body.operation) {
|
||||
if (h[`${method}_${ctx.request.body.operation}`])
|
||||
if (method === 'post' && ctx.request.body.operation) {
|
||||
if (h[`${method}_${ctx.request.body.operation}`]) {
|
||||
await h[`${method}_${ctx.request.body.operation}`](args);
|
||||
}
|
||||
}
|
||||
|
||||
if (h.cleanup) await h.cleanup(args);
|
||||
@@ -236,34 +268,35 @@ function Route(route, handler) {
|
||||
|
||||
class ConnectionHandler {
|
||||
/**
|
||||
* @param {import('sockjs').Connection} conn
|
||||
* @param {import('sockjs').Connection} conn
|
||||
*/
|
||||
constructor(conn) {
|
||||
let that = this;
|
||||
const that = this;
|
||||
this.conn = conn;
|
||||
this.request = {
|
||||
cookies: {
|
||||
get(name) {
|
||||
return that.request.cookies[name];
|
||||
},
|
||||
set() { }
|
||||
set() { },
|
||||
},
|
||||
params: {},
|
||||
headers: conn.headers
|
||||
headers: conn.headers,
|
||||
};
|
||||
this._handler = {};
|
||||
let p = (conn.url.split('?')[1] || '').split('&');
|
||||
for (let i in p) p[i] = p[i].split('=');
|
||||
for (let i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
|
||||
const p = (conn.url.split('?')[1] || '').split('&');
|
||||
for (const i in p) p[i] = p[i].split('=');
|
||||
for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
|
||||
}
|
||||
|
||||
renderHTML(name, context) {
|
||||
console.time(name);
|
||||
this.hasPerm = perm => this.user.hasPerm(perm);
|
||||
this.hasPerm = (perm) => this.user.hasPerm(perm);
|
||||
return new Promise((resolve, reject) => {
|
||||
template.render(name, Object.assign(context, {
|
||||
handler: this,
|
||||
_: str => str ? str.toString().translate(this.user.language) : '',
|
||||
user: this.user
|
||||
_: (str) => (str ? str.toString().translate(this.user.language) : ''),
|
||||
user: this.user,
|
||||
}), (error, res) => {
|
||||
console.timeEnd(name);
|
||||
if (error) reject(error);
|
||||
@@ -271,38 +304,43 @@ class ConnectionHandler {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
send(data) {
|
||||
this.conn.write(JSON.stringify(data));
|
||||
}
|
||||
|
||||
close(code, reason) {
|
||||
this.conn.close(code, reason);
|
||||
}
|
||||
|
||||
async ___prepare() {
|
||||
await new Promise((resolve, reject) => {
|
||||
this.conn.once('data', msg => {
|
||||
for (let i of msg.split(';')) {
|
||||
i = i.trim().split('=');
|
||||
this.request.cookies[i[0]] = i[1];
|
||||
this.conn.once('data', (msg) => {
|
||||
for (const i of msg.split(';')) {
|
||||
const [k, v] = i.trim().split('=');
|
||||
this.request.cookies[k] = v;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
setTimeout(reject, 5000);
|
||||
});
|
||||
this._handler.sid = this.request.cookies.get('sid');
|
||||
this.session = this._handler.sid ? await token.get(this._handler.sid, token.TYPE_SESSION) : { uid: 1 };
|
||||
this.session = this._handler.sid
|
||||
? await token.get(this._handler.sid, token.TYPE_SESSION)
|
||||
: { uid: 1 };
|
||||
if (!this.session) this.session = { uid: 1 };
|
||||
let bdoc = await blacklist.get(this.request.ip);
|
||||
const bdoc = await blacklist.get(this.request.ip);
|
||||
if (bdoc) throw new BlacklistedError(this.request.ip);
|
||||
this.user = await user.getById(this.session.uid);
|
||||
if (!this.user) throw new UserNotFoundError(this.session.uid);
|
||||
}
|
||||
}
|
||||
function Connection(prefix, handler) {
|
||||
function Connection(prefix, RouteConnHandler) {
|
||||
const sock = sockjs.createServer({ prefix });
|
||||
sock.on('connection', async conn => {
|
||||
let h = new handler(conn);
|
||||
sock.on('connection', async (conn) => {
|
||||
const h = new RouteConnHandler(conn);
|
||||
try {
|
||||
let args = Object.assign({}, h.request.params);
|
||||
const args = { ...h.request.params };
|
||||
|
||||
if (args.uid) args.uid = parseInt(validator.checkUid(args.uid));
|
||||
if (args.page) args.page = parseInt(args.page);
|
||||
@@ -313,9 +351,11 @@ function Connection(prefix, handler) {
|
||||
if (h.__prepare) await h.__prepare(args);
|
||||
if (h._prepare) await h._prepare(args);
|
||||
if (h.prepare) await h.prepare(args);
|
||||
if (h.message) conn.on('data', data => {
|
||||
h.message(JSON.parse(data));
|
||||
});
|
||||
if (h.message) {
|
||||
conn.on('data', (data) => {
|
||||
h.message(JSON.parse(data));
|
||||
});
|
||||
}
|
||||
conn.on('close', async () => {
|
||||
if (h.cleanup) await h.cleanup(args);
|
||||
if (h._cleanup) await h._cleanup(args);
|
||||
|
||||
+9
-8
@@ -1,19 +1,20 @@
|
||||
const
|
||||
Mongo = require('mongodb'),
|
||||
options = require('./options');
|
||||
const Mongo = require('mongodb');
|
||||
const options = require('./options');
|
||||
|
||||
async function run() {
|
||||
let mongourl = 'mongodb://';
|
||||
if (options.db.username) mongourl += options.db.username + ':' + options.db.password + '@';
|
||||
if (options.db.username) mongourl += `${options.db.username}:${options.db.password}@`;
|
||||
mongourl += `${options.db.host}:${options.db.port}/${options.db.name}`;
|
||||
let Database = await Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
let db = Database.db(options.db.name);
|
||||
const Database = await Mongo.MongoClient.connect(mongourl, {
|
||||
useNewUrlParser: true, useUnifiedTopology: true,
|
||||
});
|
||||
const db = Database.db(options.db.name);
|
||||
await db.dropDatabase();
|
||||
console.log('Dropped');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run().catch(e => {
|
||||
run().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
+6
-8
@@ -5,13 +5,12 @@ const map = [
|
||||
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
||||
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8',
|
||||
'9', '0'
|
||||
'9', '0',
|
||||
];
|
||||
|
||||
String.random = function (digit) {
|
||||
String.random = function random(digit) {
|
||||
let str = '';
|
||||
for (let i = 1; i <= digit; i++)
|
||||
str += map[Math.floor(Math.random() * 62)];
|
||||
for (let i = 1; i <= digit; i++) str += map[Math.floor(Math.random() * 62)];
|
||||
return str;
|
||||
};
|
||||
|
||||
@@ -20,15 +19,14 @@ String.random = function (digit) {
|
||||
* @param {Array} b
|
||||
*/
|
||||
Array.isDiff = function isDiff(a, b) {
|
||||
if (a.length != b.length) return true;
|
||||
if (a.length !== b.length) return true;
|
||||
a.sort();
|
||||
b.sort();
|
||||
for (let i in a)
|
||||
if (a[i] != b[i]) return true;
|
||||
for (const i in a) { if (a[i] !== b[i]) return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
Date.prototype.format = function (fmt = '%Y-%m-%d %H:%M:%S') {
|
||||
Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') {
|
||||
return fmt
|
||||
.replace('%Y', this.getFullYear())
|
||||
.replace('%m', this.getMonth() + 1)
|
||||
|
||||
+5
-3
@@ -13,7 +13,7 @@
|
||||
"koa": "^2.11.0",
|
||||
"koa-body": "^4.1.1",
|
||||
"koa-morgan": "^1.0.1",
|
||||
"koa-router": "^7.4.0",
|
||||
"koa-router": "^8.0.8",
|
||||
"koa-static-cache": "^5.1.2",
|
||||
"lodash": "^4.17.15",
|
||||
"markdown-it": "^10.0.0",
|
||||
@@ -24,7 +24,9 @@
|
||||
"sockjs": "^0.3.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.6.0"
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-base": "^14.1.0",
|
||||
"eslint-plugin-import": "^2.20.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
@@ -44,4 +46,4 @@
|
||||
"bin": {
|
||||
"judger": "hydro/development.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
<input type="button" class="rounded button nojs--hide" name="dczcomments__box__cancel" value="{{ _('Cancel') }} (Esc)">
|
||||
</div>
|
||||
<input type="hidden" name="operation" value="{{ comment_post_op }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="nojs--hide">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<input type="button" class="rounded button nojs--hide" name="dczcomments__box__cancel" value="{{ _('Cancel') }} (Esc)">
|
||||
</div>
|
||||
<input type="hidden" name="operation" value="{{ comment_post_op }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="nojs--hide">
|
||||
@@ -91,13 +91,13 @@
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="upvote">
|
||||
<input type="hidden" name="psid" value="{{ doc._id }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="vote-button upvote{% if pssdict[doc._id].vote == 1 %} active{% endif %}" data-tooltip="{{ _('Upvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Upvote') }}"><span class="icon icon-vote--up"></span></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="downvote">
|
||||
<input type="hidden" name="psid" value="{{ doc._id }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="vote-button downvote{% if pssdict[doc._id].vote == -1 %} active{% endif %}" data-tooltip="{{ _('Downvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Downvote') }}"><span class="icon icon-vote--down"></span></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
markdown:true
|
||||
}) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
{% if page_name == 'contest_edit' %}
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<form method="post">
|
||||
{% include "partials/discussion_edit_form.html" %}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Create') }} (Ctrl+Enter)
|
||||
</button>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
{% if handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
||||
<li><form class="form--inline" method="post">
|
||||
<input type="hidden" name="operation" value="{% if not dsdoc['star'] %}star{% else %}unstar{% endif %}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="star{% if dsdoc['star'] %} activated{% endif %}" type="submit">
|
||||
<span class="starred--hide"><span class="icon icon-star--outline"></span> {{ _('Star Topic') }}</span>
|
||||
<span class="starred--show"><span class="icon icon-star"></span> {{ _('Unstar Topic') }}</span>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<form method="post">
|
||||
{% include "partials/discussion_edit_form.html" %}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button name="operation" value="update" type="submit" class="rounded primary button">
|
||||
{{ _('Update') }} (Ctrl+Enter)
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
<div class="section__body typo">
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
{% if join_settings['method'] == vj4.constant.domain.JOIN_METHOD_CODE %}
|
||||
{{ form.form_text(columns=None, label='Invitation Code', name='code', required=true, help_text='You need to enter the invitation code to join the domain.', value=code, autofocus=True) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="section__tools">
|
||||
<form method="post">
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
<span class="icon icon-add"></span> {{ _('Initialize') }}
|
||||
</button>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{% include "partials/domain_edit_form.html" %}
|
||||
{% endwith %}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
</button>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
name='invitation_code',
|
||||
help_text='The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.',
|
||||
value=join_settings['code']|default('')) }}
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="rounded primary button" role="submit">
|
||||
{{ _('Update Settings') }}
|
||||
</button>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="rounded primary button" role="submit">
|
||||
{{ _('Update Permission') }}
|
||||
</button>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<p>{{ _('Image Markdown') }}: <pre> }})</pre></p>
|
||||
{% endif %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<p>
|
||||
{{ _('New file') }}:
|
||||
<input type="text" name="desc" placeholder="{{ _('Description') }}">
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</td>
|
||||
<td class="col--action"><form method="post">
|
||||
<input type="hidden" name="domain_id" value="{{ ddoc['_id'] }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="typo-a">{{ _('Continue') }}</button>
|
||||
</form></td>
|
||||
</tr>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{% include "partials/domain_edit_form.html" %}
|
||||
{% endwith %}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Create') }}
|
||||
</button>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{{ form.form_text(type='text', label='Current Email', columns=10, name='current_email', value=handler.user['mail'], disabled=true) }}
|
||||
{{ form.form_text(type='text', label='New Email', columns=10, name='mail', required=true) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="operation" value="change_mail">
|
||||
<input type="submit" class="rounded primary button" value="{{ _('Change Email') }}">
|
||||
</div></div>
|
||||
@@ -32,7 +32,7 @@
|
||||
{{ form.form_text(type='password', label='New Password', columns=10, name='new_password', required=true) }}
|
||||
{{ form.form_text(type='password', label='Repeat Password', columns=10, name='verify_password', required=true) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="operation" value="change_password">
|
||||
<input type="submit" class="rounded primary button" value="{{ _('Change Password') }}">
|
||||
</div></div>
|
||||
@@ -72,7 +72,7 @@
|
||||
<input type="hidden" name="operation" value="delete_token">
|
||||
<input type="hidden" name="token_type" value="{{ session['token_type'] }}">
|
||||
<input type="hidden" name="token_digest" value="{{ session['token_digest'] }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" value="{{ _('Logout This Session') }}" class="rounded button">
|
||||
</form>
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@
|
||||
<div class="section__body">
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="delete_all_tokens">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" value="{{ _('Logout All Sessions') }}" class="primary rounded button">
|
||||
</form>
|
||||
<div class="typo">
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="section__body">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" class="rounded primary button" value="{{ _('Save All Changes') }}">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{{ form.form_text(columns=None, label='Problems', name='pids', value=pids) }}
|
||||
{{ form.form_textarea(columns=None, label='Content', name='content', value=tdoc['content']|default(''), markdown=True) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
{% if page_name == 'homework_edit' %}
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<meta name="theme-color" content="#56758f">
|
||||
|
||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
||||
<title>{{ handler.render_title(page_title) }}</title>
|
||||
<title>{{ handler.renderTitle(page_title) }}</title>
|
||||
<script>
|
||||
var _htmlNode = document.documentElement;
|
||||
_htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs');
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{% if handler.hasPerm(perm.PERM_ATTEND_CONTEST) and handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
||||
<form action="/c/{{ tdoc._id }}" method="POST">
|
||||
<input type="hidden" name="operation" value="attend">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="menu__link" type="submit">
|
||||
<span class="icon icon-add"></span> {{ _('Attend Contest') }}
|
||||
</button>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{% if handler.hasPerm(perm.PERM_ATTEND_HOMEWORK) and handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
||||
<form action="{{ reverse_url('homework_detail', tid=tdoc['doc_id']) }}" method="POST">
|
||||
<input type="hidden" name="operation" value="attend">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="menu__link" type="submit">
|
||||
<span class="icon icon-add"></span> {{ _('Claim Homework') }}
|
||||
</button>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
{% if page_name == 'problem_edit' %}
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Copy') }}
|
||||
</button>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
value:pdoc['tag']|join(', ')
|
||||
}) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
</button>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
}) }}
|
||||
{{ form.form_textarea({label:'Code', columns:12, name:'code', extra_class:'monospace', autofocus:true}) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" class="rounded primary button" value="{{ _('Submit') }}">
|
||||
</div></div>
|
||||
</form>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p>{{ _('MD5: {0}').format(md5 or _('(None)')) }}</p>
|
||||
{% endif %}
|
||||
<p>{{ _('New dataset') }}:
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="{{ _('Upload') }}" class="rounded primary button">
|
||||
</p>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<ol class="menu">
|
||||
<li class="menu__item">
|
||||
<form method="post" action="/r/{{ rdoc._id }}/rejudge">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="menu__link">
|
||||
<span class="icon icon-refresh"></span> {{ _('Rejudge') }}
|
||||
</button>
|
||||
@@ -42,7 +42,7 @@
|
||||
<li class="menu__item">
|
||||
<form method="post" action="/judge/end">
|
||||
<input type="hidden" name="rid" value="{{ rdoc._id }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="score" value="0">
|
||||
<input type="hidden" name="message" value="{{ _('score canceled') }}">
|
||||
<button type="submit" class="menu__link">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<td class="col--problem col--problem-name">
|
||||
{% if handler.hasPerm(perm.PERM_REJUDGE) %}
|
||||
<form class="form--inline" method="post" action="/r/{{ rdoc._id }}/rejudge">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button type="submit" class="link text-maroon lighter">
|
||||
<span class="icon icon-refresh"></span>
|
||||
{{ _('Rejudge') }}
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
<form class="form--inline" action="{{ reverse_url('problem_main') }}" method="post">
|
||||
<input type="hidden" name="pid" value="{{ pdoc['_id'] }}">
|
||||
<input type="hidden" name="operation" value="{% if not psdoc['star'] %}star{% else %}unstar{% endif %}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="star{% if psdoc['star'] %} activated{% endif %}" type="submit">
|
||||
<span class="starred--hide"><span class="icon icon-star--outline" data-tooltip="{{ _('Star') }}"></span></span>
|
||||
<span class="starred--show"><span class="icon icon-star" data-tooltip="{{ _('Unstar') }}"></span></span>
|
||||
@@ -168,7 +168,7 @@
|
||||
<li class="menu__item">
|
||||
<form action="" method="POST">
|
||||
<input type="hidden" name="operation" value="enroll">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<button class="menu__link" type="submit">
|
||||
<span class="icon icon-add"></span> {{ _('Enroll Training') }}
|
||||
</button>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
{% if page_name == 'training_edit' %}
|
||||
<button type="submit" class="rounded primary button">
|
||||
{{ _('Update') }}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="immersive--content immersive--center">
|
||||
<h1>{{ _('Logout') }}</h1>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<div class="text-center">
|
||||
<input type="submit" value="{{ _('Logout') }}" class="inverse rounded primary button">
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user