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