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

    {{ _('Image Markdown') }}:

    ![{{ _('Description') }}](vijos:/{{ reverse_url('fs_get', domain_id=vj4.model.builtin.DOMAIN_ID_SYSTEM, secret=fdoc['metadata']['secret']) }})

    {% endif %} - +

    {{ _('New file') }}: diff --git a/templates/home_domain.html b/templates/home_domain.html index 036330337..603d6ff98 100644 --- a/templates/home_domain.html +++ b/templates/home_domain.html @@ -47,7 +47,7 @@ - + diff --git a/templates/home_domain_create.html b/templates/home_domain_create.html index c7fde65b6..036e4c546 100644 --- a/templates/home_domain_create.html +++ b/templates/home_domain_create.html @@ -21,7 +21,7 @@ {% include "partials/domain_edit_form.html" %} {% endwith %}

    - + diff --git a/templates/home_security.html b/templates/home_security.html index fec3c826d..80b47c312 100644 --- a/templates/home_security.html +++ b/templates/home_security.html @@ -13,7 +13,7 @@ {{ form.form_text(type='text', label='Current Email', columns=10, name='current_email', value=handler.user['mail'], disabled=true) }} {{ form.form_text(type='text', label='New Email', columns=10, name='mail', required=true) }}
    - +
    @@ -32,7 +32,7 @@ {{ form.form_text(type='password', label='New Password', columns=10, name='new_password', required=true) }} {{ form.form_text(type='password', label='Repeat Password', columns=10, name='verify_password', required=true) }}
    - +
    @@ -72,7 +72,7 @@ - +
    @@ -85,7 +85,7 @@
    - +
    diff --git a/templates/home_settings.html b/templates/home_settings.html index e7e556d1a..905204715 100644 --- a/templates/home_settings.html +++ b/templates/home_settings.html @@ -61,7 +61,7 @@
    {% endfor %}
    - +
    diff --git a/templates/homework_edit.html b/templates/homework_edit.html index b93c6252d..063d4edf5 100644 --- a/templates/homework_edit.html +++ b/templates/homework_edit.html @@ -23,7 +23,7 @@ {{ form.form_text(columns=None, label='Problems', name='pids', value=pids) }} {{ form.form_textarea(columns=None, label='Content', name='content', value=tdoc['content']|default(''), markdown=True) }}
    - + {% if page_name == 'homework_edit' %}