diff --git a/.gitignore b/.gitignore index 93066a51..f5214ea4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ dist/ __* ui/misc/.iconfont ui/static/locale -.webpackStats.json +*.log !.gitkeep diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 83bed29f..210ab878 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -10,6 +10,25 @@ const hpm = require('../lib/hpm'); const loader = require('../loader'); const check = require('../check'); +function set(key, value) { + if (setting.SYSTEM_SETTINGS_BY_KEY[key]) { + const s = setting.SYSTEM_SETTINGS_BY_KEY[key]; + if (s.flag & setting.FLAG_DISABLED) return undefined; + if (s.type === 'boolean') { + if (value === 'on') return true; + return false; + } + if (s.type === 'number') { + if (!Number.isSafeInteger(parseInt(value, 10))) { + throw new ValidationError(key); + } + return parseInt(value, 10); + } + return value; + } + return undefined; +} + class SystemHandler extends Handler { async prepare() { this.checkPriv(PRIV.PRIV_EDIT_SYSTEM); @@ -147,21 +166,14 @@ class SystemSettingHandler extends SystemHandler { const tasks = []; for (const key in args) { if (typeof args[key] === 'object') { - const subtasks = []; - for (const sub in args[key]) { - const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`]; - if (s && !((s.flag & setting.FLAG_SECRET) && args[key][sub] === '')) { - if (s.ui === 'number') args[key][sub] = Number(args[key][sub]); - subtasks.push(system.set(`${key}.${sub}`, args[key][sub])); + for (const subkey in args[key]) { + console.log(`${key}.${subkey}`, set(`${key}.${subkey}`, args[key][subkey])); + if (typeof set(`${key}.${subkey}`, args[key][subkey]) !== 'undefined') { + tasks.push(system.set(`${key}.${subkey}`, set(`${key}.${subkey}`, args[key][subkey]))); } } - tasks.push(Promise.all(subtasks)); - } else { - const s = setting.SYSTEM_SETTINGS_BY_KEY[key]; - if (s && !((s.flag & setting.FLAG_SECRET) && args[key] === '')) { - if (s.ui === 'number') args[key] = Number(args[key]); - tasks.push(system.set(key, args[key])); - } + } else if (typeof set(key, args[key]) !== 'undefined') { + tasks.push(system.set(key, set(key, args[key]))); } } await Promise.all(tasks); diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 47d5c0a1..e49ae23d 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -12,7 +12,7 @@ const { PERM_CREATE_PROBLEM, PERM_READ_PROBLEM_DATA, PERM_EDIT_PROBLEM, 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, + PERM_REPLY_PROBLEM_SOLUTION, PERM_EDIT_PROBLEM_SELF, }, PRIV: { PRIV_USER_PROFILE, PRIV_JUDGE, @@ -202,11 +202,8 @@ class ProblemSubmitHandler extends ProblemDetailHandler { const { lang, code } = this.request.body; const rid = await record.add(domainId, { uid: this.user._id, lang, code, pid: this.pdoc.docId, - }); - await Promise.all([ - record.judge(domainId, rid), - user.incDomain(domainId, this.user._id, 'nSubmit'), - ]); + }, true); + await user.incDomain(domainId, this.user._id, 'nSubmit'); this.response.body = { rid }; this.response.redirect = this.url('record_detail', { rid }); } @@ -264,6 +261,7 @@ class ProblemStatisticsHandler extends ProblemDetailHandler { class ProblemManageHandler extends ProblemDetailHandler { async prepare() { if (this.pdoc.owner !== this.user._id) this.checkPerm(PERM_EDIT_PROBLEM); + else this.checkPerm(PERM_EDIT_PROBLEM_SELF); } } @@ -475,7 +473,7 @@ class ProblemCreateHandler extends Handler { pid, hidden, }); this.response.body = { pid }; - this.response.redirect = `/p/${pid}/settings`; + this.response.redirect = this.url('problem_settings', { pid }); } } diff --git a/hydro/handler/system.js b/hydro/handler/system.js deleted file mode 100644 index 26bdd5f9..00000000 --- a/hydro/handler/system.js +++ /dev/null @@ -1,98 +0,0 @@ -const domain = require('../model/domain'); -const system = require('../model/system'); -const setting = require('../model/setting'); -const { Route, Handler } = require('../service/server'); -const { PRIV_EDIT_SYSTEM } = require('../model/builtin').PRIV; -const hpm = require('../lib/hpm'); -const loader = require('../loader'); - -class SystemHandler extends Handler { - async prepare({ domainId }) { - this.checkPriv(PRIV_EDIT_SYSTEM); - this.domain = await domain.get(domainId); - } -} - -class SystemDashboardHandler extends SystemHandler { - async get() { - const path = [ - ['Hydro', 'homepage'], - ['manage', null], - ['manage_dashboard', null], - ]; - this.response.template = 'manage_dashboard.html'; - this.response.body = { domain: this.domain, path }; - } -} - -class SystemModuleHandler extends SystemHandler { - async get() { - const installed = await hpm.getInstalled(); - const path = [ - ['Hydro', 'homepage'], - ['manage', null], - ['manage_module', null], - ]; - this.response.body = { installed, active: loader.active, path }; - this.response.template = 'manage_module.html'; - } - - async postInstall({ url, id }) { - await hpm.install(url, id); - this.back(); - } - - async postDelete({ id }) { - await hpm.del(id); - this.back(); - } -} - -class SystemSettingHandler extends SystemHandler { - async get() { - this.response.template = 'manage_settings.html'; - const current = {}; - const settings = setting.SYSTEM_SETTINGS; - for (const s of settings) { - // eslint-disable-next-line no-await-in-loop - current[s.key] = await system.get(s.key); - } - this.response.body = { - current, settings, - }; - } - - async post(args) { - const tasks = []; - for (const key in args) { - if (typeof args[key] === 'object') { - const subtasks = []; - for (const sub in args[key]) { - if (setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`]) { - const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`]; - if (s.ui === 'checkbox') { - if (args[key][sub] === 'on') { - subtasks.push(system.set(`${key}.${sub}`, true)); - } else { - subtasks.push(system.set(`${key}.${sub}`, false)); - } - } else { - subtasks.push(system.set(`${key}.${sub}`, args[key][sub])); - } - } - } - tasks.push(Promise.all(subtasks)); - } else tasks.push(system.set(key, args[key])); - } - await Promise.all(tasks); - this.back(); - } -} - -async function apply() { - Route('system_dashboard', '/system', SystemDashboardHandler); - Route('system_module', '/system/module', SystemModuleHandler); - Route('system_setting', '/system/setting', SystemSettingHandler); -} - -global.Hydro.handler.manage = module.exports = apply; diff --git a/hydro/model/record.js b/hydro/model/record.js index 2dfc6bee..926202e9 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -73,13 +73,13 @@ function getMulti(domainId, query) { async function update(domainId, rid, $set, $push, $unset) { const _id = new ObjectID(rid); - const upd = {}; - if ($set && Object.keys($set).length) upd.$set = $set; - if ($push && Object.keys($push).length) upd.$push = $push; - if ($unset && Object.keys($unset).length) upd.$unset = $unset; - const rdoc = await coll.findOneAndUpdate({ domainId, _id }, upd, { returnOriginal: false }); - if (!rdoc) throw new RecordNotFoundError(rid); - return rdoc; + const $update = {}; + if ($set && Object.keys($set).length) $update.$set = $set; + if ($push && Object.keys($push).length) $update.$push = $push; + if ($unset && Object.keys($unset).length) $update.$unset = $unset; + const res = await coll.findOneAndUpdate({ domainId, _id }, $update, { returnOriginal: false }); + if (!res.value) throw new RecordNotFoundError(rid); + return res.value; } function reset(domainId, rid) { diff --git a/hydro/service/bus.js b/hydro/service/bus.js index 500a145b..1e1784d7 100644 --- a/hydro/service/bus.js +++ b/hydro/service/bus.js @@ -21,6 +21,7 @@ function unsubscribe(events, handler, funcName) { handler.__bus = (...args) => { handler[funcName].call(handler, ...args); }; + // FIXME doesn't work for (const event of events) bus.off(event, handler.__bus); delete handler.__bus; } diff --git a/hydro/service/server.js b/hydro/service/server.js index 53300e5d..701d5954 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -207,8 +207,9 @@ const Handler = HandlerMixin(class { /** * @param {import('koa').Context} ctx */ - constructor(ctx) { + constructor(ctx, args) { this.ctx = ctx; + this.args = args; this.request = { host: ctx.request.host, hostname: ctx.request.hostname, @@ -223,7 +224,7 @@ const Handler = HandlerMixin(class { referer: ctx.request.headers.referer || '/', }; this.response = { - body: '', + body: {}, type: '', status: null, template: null, @@ -274,8 +275,6 @@ const Handler = HandlerMixin(class { } async init({ domainId }) { - this.response.body = {}; - this.now = new Date(); this.preferJson = (this.request.headers.accept || '').includes('application/json'); await Promise.all([ this.getSession(), @@ -292,7 +291,7 @@ const Handler = HandlerMixin(class { if (!sdoc || sdoc.uid !== this.user._id) throw new CsrfTokenError(csrfToken); } - async ___cleanup() { + async finish() { try { await this.renderBody(); } catch (error) { @@ -385,26 +384,25 @@ const Handler = HandlerMixin(class { this.response.body = { error: { message: error.msg(), params: error.params, stack: error.stack }, }; - await this.___cleanup().catch(() => { }); + await this.finish().catch(() => { }); } }); async function handle(ctx, HandlerClass, checker) { global.Hydro.stat.reqCount++; - const h = new HandlerClass(ctx); + const args = { + domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, + }; + const h = new HandlerClass(ctx, args); try { const method = ctx.method.toLowerCase(); - const args = { - domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, - }; - h.args = args; let operation; if (method === 'post' && ctx.request.body.operation) { operation = `_${ctx.request.body.operation}` .replace(/_([a-z])/gm, (s) => s[1].toUpperCase()); } - if (h.init) await h.init(args); + await h.init(args); if (checker) checker.call(h); if (method === 'post') { await h.checkCsrfToken(args.csrfToken); @@ -432,51 +430,47 @@ async function handle(ctx, HandlerClass, checker) { throw new ValidationError(`Argument ${checking} check failed`); } - if (h.__prepare) await h.__prepare(args); if (h._prepare) await h._prepare(args); if (h.prepare) await h.prepare(args); - if (h[`___${method}`]) await h[`___${method}`](args); - if (h[`__${method}`]) await h[`__${method}`](args); - if (h[`_${method}`]) await h[`_${method}`](args); if (h[method]) await h[method](args); if (operation) await h[`post${operation}`](args); if (h.cleanup) await h.cleanup(args); - if (h._cleanup) await h._cleanup(args); - if (h.__cleanup) await h.__cleanup(args); - if (h.___cleanup) await h.___cleanup(args); + if (h.finish) await h.finish(args); } catch (e) { await h.onerror(e); } } -const Checker = (perm, priv, checker) => function __() { - checker(); - if (perm) this.checkPerm(perm); - if (priv) this.checkPriv(priv); -}; - -function Route(name, route, RouteHandler, ...permPrivChecker) { - let _priv; - let _perm; +const Checker = (permPrivChecker) => { + let perm; + let priv; let checker = () => { }; for (const item of permPrivChecker) { if (typeof item === 'object') { if (typeof item.call !== 'undefined') { checker = item; } if (typeof item[0] === 'number') { - _priv = item; + priv = item; } else if (typeof item[0] === 'string') { - _perm = item; + perm = item; } } else if (typeof item === 'number') { - _priv = item; + priv = item; } else if (typeof item === 'string') { - _perm = item; + perm = item; } } - checker = Checker(_perm, _priv, checker); + return function check() { + checker(); + if (perm) this.checkPerm(perm); + if (priv) this.checkPriv(priv); + }; +}; + +function Route(name, route, RouteHandler, ...permPrivChecker) { + const checker = Checker(permPrivChecker); router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker)); router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker)); } @@ -496,17 +490,6 @@ const ConnectionHandler = HandlerMixin(class { for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]); } - async renderHTML(name, context) { - this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; - const res = await template.render(name, Object.assign(context, { - handler: this, - url: (...args) => this.url(...args), - _: (str) => (str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''), - user: this.user, - })); - return res; - } - send(data) { this.conn.write(JSON.stringify(data)); } @@ -522,10 +505,9 @@ const ConnectionHandler = HandlerMixin(class { async init({ domainId }) { try { - this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN); - await token.del(this.request.params.token, token.TYPE_CSRF_TOKEN); + this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN, true); } catch (e) { - this.session = { uid: 1 }; + this.session = { uid: 0 }; } const bdoc = await blacklist.get(this.request.ip); if (bdoc) throw new BlacklistedError(this.request.ip); @@ -534,15 +516,16 @@ const ConnectionHandler = HandlerMixin(class { } }); -function Connection(name, prefix, RouteConnHandler, permission) { +function Connection(name, prefix, RouteConnHandler, ...permPrivChecker) { const sock = sockjs.createServer({ prefix }); + const checker = Checker(permPrivChecker); sock.on('connection', async (conn) => { const h = new RouteConnHandler(conn); try { const args = { domainId: 'system', ...h.request.params }; h.args = args; - if (h.init) await h.init(args); - if (permission) h.checkPerm(permission); + await h.init(args); + checker.call(h); let checking = ''; try { for (const key in validate) { @@ -555,7 +538,6 @@ function Connection(name, prefix, RouteConnHandler, permission) { if (e instanceof ValidationError) throw e; throw new ValidationError(`Argument ${checking} check failed`); } - if (h.__prepare) await h.__prepare(args); if (h._prepare) await h._prepare(args); if (h.prepare) await h.prepare(args); if (h.message) { @@ -565,9 +547,7 @@ function Connection(name, prefix, RouteConnHandler, permission) { } conn.on('close', async () => { if (h.cleanup) await h.cleanup(args); - if (h._cleanup) await h._cleanup(args); - if (h.__cleanup) await h.__cleanup(args); - if (h.___cleanup) await h.___cleanup(args); + if (h.finish) await h.finish(args); }); } catch (e) { console.log(e); diff --git a/templates/problem_settings.html b/templates/problem_settings.html index 7cf6d98f..6c6cdd44 100644 --- a/templates/problem_settings.html +++ b/templates/problem_settings.html @@ -64,7 +64,7 @@ value:pdoc['tag']|join(', ') }) }}
- +