diff --git a/.eslintrc.js b/.eslintrc.js index 8327613bc..18980dfb8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,5 +26,6 @@ module.exports = { "no-param-reassign": "off", "global-require": "off", "no-nested-ternary": "off", + "no-multi-assign": "off", }, }; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 658739e33..cac58e4a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,8 +14,7 @@ jobs: node_version: '13.x' - run: | yarn - yarn add webpack webpack-cli -D - yarn build + yarn build:production - uses: actions/upload-artifact@v1 with: name: hydro @@ -23,8 +22,7 @@ jobs: - run: | cd ui yarn - yarn add webpack webpack-cli -D - yarn build + yarn build:production cd .. - uses: actions/upload-artifact@v1 with: diff --git a/Dockerfile b/Dockerfile index 89d88097b..e1432cf6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM mhart/alpine-node:slim-10 WORKDIR /app -ADD .uibuild dist locales templates /app/ +ADD .uibuild .build /app/ RUN mkdir /config VOLUME [ "/config" ] EXPOSE 8888 -CMD ["node", "dist/development.js"] +CMD ["node", ".build/development.js"] diff --git a/build/buildModule.js b/build/buildModule.js index 3c03d8351..72f30cc31 100644 --- a/build/buildModule.js +++ b/build/buildModule.js @@ -1,7 +1,9 @@ +/* eslint-disable import/no-extraneous-dependencies */ const fs = require('fs'); const webpack = require('webpack'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const root = require('./root'); + const exist = (name) => { try { fs.statSync(root(name)); @@ -9,24 +11,24 @@ const exist = (name) => { return false; } return true; -} -const build = async () => { +}; +const build = async (type) => { const modules = fs.readdirSync(root('module')); const config = { - mode: 'production', + mode: type, entry: {}, output: { filename: 'module/[name].js', - path: root('.build') + path: root('.build'), }, target: 'node', module: {}, plugins: [ new webpack.ProgressPlugin(), new FriendlyErrorsPlugin(), - ] + ], }; - for (let i of modules) { + for (const i of modules) { if (!i.startsWith('.')) { if (exist(`module/${i}/model.js`)) { config.entry[`${i}/model`] = root(`module/${i}/model.js`); @@ -47,7 +49,7 @@ const build = async () => { if (stats.hasErrors()) process.exitCode = 1; resolve(); }); - }) -} + }); +}; module.exports = build; diff --git a/build/buildTemplate.js b/build/buildTemplate.js new file mode 100644 index 000000000..b1a18ec9d --- /dev/null +++ b/build/buildTemplate.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const yaml = require('js-yaml'); +const root = require('./root'); + +function getFiles(folder) { + const files = []; + const f = fs.readdirSync(root(folder)); + for (const i of f) { + if (!i.startsWith('.')) { + if (fs.statSync(root(`${folder}/${i}`)).isDirectory()) { + const g = getFiles(`${folder}/${i}`); + for (const j of g) files.push(`${i}/${j}`); + } else files.push(i); + } + } + return files; +} + +const build = async () => { + const files = getFiles('templates'); + const exclude = ['partials/training_default.json', 'partials/problem_default.md']; + const templates = {}; + for (const i of files) { + let template = fs.readFileSync(root(`templates/${i}`)).toString(); + if (!exclude.includes(i)) { + template = template + .trim() + .replace(/ *\n */gmi, ' ') + .replace(/, /gmi, ',') + .replace(/%} {%/gmi, '%}{%') + .replace(/ %}/gmi, '%}') + .replace(/{% /gmi, '{%') + .replace(/> <') + .replace(/}} {{/gmi, '>{{') + .replace(/{{ /gmi, '{{') + .replace(/ }}/gmi, '}}') + .trim(); + } + templates[i] = template; + } + fs.writeFileSync(root('.build/template.yaml'), yaml.safeDump(templates)); +}; + +module.exports = build; diff --git a/build/development.js b/build/development.js new file mode 100644 index 000000000..efabfb975 --- /dev/null +++ b/build/development.js @@ -0,0 +1,3 @@ +require('./index.js')('development').catch((e) => { + console.error(e); +}); diff --git a/build/index.js b/build/index.js index 928cd64b5..1a9f218d3 100644 --- a/build/index.js +++ b/build/index.js @@ -1,9 +1,24 @@ const fs = require('fs'); const root = require('./root'); -try { - fs.rmdirSync(root('.build')); -} catch (e) { } -fs.mkdirSync(root('.build')); -require('./locales')(); -require('./buildModule')(); -require('./webpack')(); + +async function build(type) { + if (!['development', 'production'].includes(type)) throw new Error('Unknown type: ' + type); + try { + fs.rmdirSync(root('.build')); + } catch (e) { } + fs.mkdirSync(root('.build')); + await Promise.all([ + require('./locales')(type), + require('./buildModule')(type), + require('./buildTemplate')(type), + require('./webpack')(type), + ]); +} + +module.exports = build; + +if (!module.parent) { + build('development').catch((e) => { + console.error(e); + }); +} diff --git a/build/locales.js b/build/locales.js index fcd6388e7..8ccde9acf 100644 --- a/build/locales.js +++ b/build/locales.js @@ -1,13 +1,14 @@ const yaml = require('js-yaml'); const fs = require('fs'); const root = require('./root'); + const build = async () => { - let langs = fs.readdirSync(root('locales')); - let lang = {}; - for (let i of langs) { + const langs = fs.readdirSync(root('locales')); + const lang = {}; + for (const i of langs) { const content = fs.readFileSync(root(`locales/${i}`)).toString(); lang[i.split('.')[0]] = yaml.safeLoad(content); } fs.writeFileSync(root('.build/locales.json'), JSON.stringify(lang)); -} +}; module.exports = build; diff --git a/build/production.js b/build/production.js new file mode 100644 index 000000000..ec842afda --- /dev/null +++ b/build/production.js @@ -0,0 +1,3 @@ +require('./index.js')('production').catch((e) => { + console.error(e); +}); diff --git a/build/root.js b/build/root.js index 19453df88..924d194a5 100644 --- a/build/root.js +++ b/build/root.js @@ -1,4 +1,5 @@ const path = require('path'); + function root(name) { return path.resolve(__dirname, '..', name); } diff --git a/build/webpack.js b/build/webpack.js index 18c12194c..871a1c71d 100644 --- a/build/webpack.js +++ b/build/webpack.js @@ -1,11 +1,13 @@ +/* eslint-disable import/no-extraneous-dependencies */ const fs = require('fs'); const webpack = require('webpack'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const root = require('./root'); -const build = async () => { + +const build = async (type) => { function hackNodeModuleFormidable() { const tasks = ['incoming_form', 'file', 'json_parser', 'querystring_parser']; - for (let task of tasks) { + for (const task of tasks) { let file = fs.readFileSync(root(`node_modules/formidable/lib/${task}.js`)).toString(); if (file.startsWith('if (global.GENTLY) require = GENTLY.hijack(require);')) { file = file.split('\n'); @@ -18,22 +20,22 @@ const build = async () => { hackNodeModuleFormidable(); const config = { - mode: 'development', + mode: type, entry: { development: root('hydro/development.js'), install: root('hydro/install.js'), - uninstall: root('hydro/uninstall.js') + uninstall: root('hydro/uninstall.js'), }, output: { filename: '[name].js', - path: root('.build') + path: root('.build'), }, target: 'node', module: {}, plugins: [ new webpack.ProgressPlugin(), new FriendlyErrorsPlugin(), - ] + ], }; const compiler = webpack(config); await new Promise((resolve, reject) => { @@ -41,12 +43,12 @@ const build = async () => { if (err) { console.error(err.stack || err); if (err.details) console.error(err.details); - reject(1); + reject(); } if (stats.hasErrors()) process.exitCode = 1; resolve(); }); - }) -} + }); +}; module.exports = build; diff --git a/hydro/development.js b/hydro/development.js index 8c7b2f13d..722db7195 100644 --- a/hydro/development.js +++ b/hydro/development.js @@ -9,7 +9,10 @@ process.stdin.on('data', async (input) => { } }); -global.Hydro = {}; +global.Hydro = { + handler: {}, + template: {} +}; require('./lib/i18n'); require('./utils'); @@ -31,21 +34,18 @@ async function run() { require('./service/gridfs'); require('./service/queue'); const server = require('./service/server'); - const HandlerHome = require('./handler/home'); - const HandlerProblem = require('./handler/problem'); - const HandlerRecord = require('./handler/record'); - const HandlerJudge = require('./handler/judge'); - const HandlerUser = require('./handler/user'); - const HandlerContest = require('./handler/contest'); - const HandlerTraining = require('./handler/training'); - const HandlerDiscussion = require('./handler/discussion'); - const HandlerManage = require('./handler/manage'); - const HandlerImport = require('./handler/import'); + const builtinHandler = [ + 'home', 'problem', 'record', 'judge', 'user', + 'contest', 'training', 'discussion', 'manage', 'import', + ]; + for (const i of builtinHandler) require(`./handler/${i}`); await loader.model(); await loader.handler(); - HandlerContest.apply(); - HandlerDiscussion.apply(); - HandlerImport.apply(); + for (const i in global.Hydro.handler) { + await global.Hydro.handler[i].apply(); + } + const notfound = require(`./handler/notfound`); + await notfound.apply(); server.start(); } run().catch((e) => { diff --git a/hydro/handler/contest.js b/hydro/handler/contest.js index a52d4d119..7e7bd97f5 100644 --- a/hydro/handler/contest.js +++ b/hydro/handler/contest.js @@ -267,7 +267,7 @@ class ContestCreateHandler extends ContestHandler { } } -function apply() { +async function apply() { Route('/c', module.exports.ContestListHandler); Route('/c/:tid', module.exports.ContestDetailHandler); Route('/c/:tid/edit', module.exports.ContestEditHandler); @@ -278,7 +278,7 @@ function apply() { Route('/contest/create', module.exports.ContestCreateHandler); } -module.exports = { +global.Hydro.handler.contest = module.exports = { ContestListHandler, ContestDetailHandler, ContestEditHandler, diff --git a/hydro/handler/discussion.js b/hydro/handler/discussion.js index c44566ed0..b3c2f19ab 100644 --- a/hydro/handler/discussion.js +++ b/hydro/handler/discussion.js @@ -34,7 +34,9 @@ class DiscussionHandler extends Handler { if (drrid) { [, this.drrdoc] = await discussion.getTailReply(drid, drrid); if (!this.drrdoc) throw new DiscussionNotFoundError(drrid); - if (this.drrdoc.parent !== this.drdoc._id) throw new DocumentNotFoundError(drid); + if (this.drrdoc.parent !== this.drdoc._id) { + throw new DocumentNotFoundError(drid); + } } } } @@ -60,7 +62,11 @@ class DiscussionHandler extends Handler { class DiscussionMainHandler extends DiscussionHandler { async get({ page = 1 }) { - const [ddocs, dpcount] = await paginate(discussion.getMulti(), page, constants.DISCUSSION_PER_PAGE); + const [ddocs, dpcount] = await paginate( + discussion.getMulti(), + page, + constants.DISCUSSION_PER_PAGE, + ); const udict = await user.getList(ddocs.map((ddoc) => ddoc.owner)); this.response.template = 'discussion_main_or_node.html'; this.response.body = { @@ -114,12 +120,11 @@ class DiscussionCreateHandler extends DiscussionHandler { type, docId, title, content, highlight, }) { this.limitRate('add_discussion', 3600, 30); - const flags = {}; - if (highlight) { - this.checkPerm(PERM_HIGHLIGHT_DISCUSSION); - flags.highlight = true; - } - const did = await discussion.add(type, docId, this.user._id, title, content, this.request.ip, flags); + if (highlight) this.checkPerm(PERM_HIGHLIGHT_DISCUSSION); + const did = await discussion.add( + type, docId, this.user._id, + title, content, this.request.ip, highlight, + ); this.response.body = { did }; this.response.redirect = `/discuss/${did}`; } @@ -257,7 +262,7 @@ class DiscussionEditHandler extends DiscussionHandler { } } -function apply() { +async function apply() { Route('/discuss', module.exports.DiscussionMainHandler); Route('/discuss/:did', module.exports.DiscussionDetailHandler); Route('/discuss/:did/edit', module.exports.DiscussionEditHandler); @@ -268,7 +273,7 @@ function apply() { Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler); } -module.exports = { +global.Hydro.handler.discussion = module.exports = { DiscussionMainHandler, DiscussionDetailHandler, DiscussionEditHandler, diff --git a/hydro/handler/home.js b/hydro/handler/home.js index a7a226a8d..fcdb3e12f 100644 --- a/hydro/handler/home.js +++ b/hydro/handler/home.js @@ -171,11 +171,16 @@ class UserChangemailWithCodeHandler extends Handler { } } -Route('/', HomeHandler); -Route('/home/security', HomeSecurityHandler); -Route('/home/changeMail/:code', UserChangemailWithCodeHandler); -Route('/home/settings/:category', HomeSettingsHandler); +async function apply() { + Route('/', module.exports.HomeHandler); + Route('/home/security', module.exports.HomeSecurityHandler); + Route('/home/changeMail/:code', module.exports.UserChangemailWithCodeHandler); + Route('/home/settings/:category', module.exports.HomeSettingsHandler); +} +global.Hydro.handler.home = module.exports = { + HomeHandler, HomeSecurityHandler, HomeSettingsHandler, UserChangemailWithCodeHandler, apply, +}; /* @app.route('/home/messages', 'home_messages', global_route=True) class HomeMessagesHandler(base.OperationHandler): diff --git a/hydro/handler/import.js b/hydro/handler/import.js index 1e9431652..e6bd284c5 100644 --- a/hydro/handler/import.js +++ b/hydro/handler/import.js @@ -1,4 +1,3 @@ - const problem = require('../model/problem'); const { Route, Handler } = require('../service/server'); const { PERM_CREATE_PROBLEM } = require('../permission'); @@ -23,8 +22,10 @@ class ProblemImportHandler extends Handler { async post({ url, pid, hidden, remoteType, }) { - if (remoteType !== 'syzoj') throw new ValidationError('remoteType'); - const [pdoc, testdata] = await this.syzoj(url); + if (typeof this[`from_${remoteType}`] !== 'function') { + throw new ValidationError('remoteType'); + } + const [pdoc, testdata] = await this[`from_${remoteType}`](url); if (pid) pdoc.pid = pid; if (hidden) pdoc.hidden = true; const _id = await problem.add(pdoc); @@ -39,6 +40,6 @@ async function apply() { Route('/problem/import', module.exports.ProblemImportHandler); } -global.Hydro['handler.import'] = module.exports = { +global.Hydro.handler.import = module.exports = { ProblemImportHandler, apply, }; diff --git a/hydro/handler/judge.js b/hydro/handler/judge.js index 16d177b85..67b1bc17f 100644 --- a/hydro/handler/judge.js +++ b/hydro/handler/judge.js @@ -183,5 +183,11 @@ class JudgeConnectionHandler extends ConnectionHandler { } } -Route('/judge', JudgeHandler); -Connection('/judge/conn', JudgeConnectionHandler); +async function apply() { + Route('/judge', module.exports.JudgeHandler); + Connection('/judge/conn', module.exports.JudgeConnectionHandler); +} + +global.Hydro.handler.judge = module.exports = { + JudgeHandler, JudgeConnectionHandler, apply, +}; diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 3863df248..346862228 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -120,9 +120,21 @@ class ManageRoleHandler extends ManageHandler { } } -Route('/manage', ManageMainHandler); -Route('/manage/dashboard', ManageDashboardHandler); -Route('/manage/edit', ManageEditHandler); -Route('/manage/user', ManageUserHandler); -Route('/manage/permission', ManagePermissionHandler); -Route('/manage/role', ManageRoleHandler); +async function apply() { + Route('/manage', module.exports.ManageMainHandler); + Route('/manage/dashboard', module.exports.ManageDashboardHandler); + Route('/manage/edit', module.exports.ManageEditHandler); + Route('/manage/user', module.exports.ManageUserHandler); + Route('/manage/permission', module.exports.ManagePermissionHandler); + Route('/manage/role', module.exports.ManageRoleHandler); +} + +global.Hydro.handler.manage = module.exports = { + ManageMainHandler, + ManageDashboardHandler, + ManageEditHandler, + ManageUserHandler, + ManagePermissionHandler, + ManageRoleHandler, + apply, +}; diff --git a/hydro/handler/notfound.js b/hydro/handler/notfound.js new file mode 100644 index 000000000..07f3e24d2 --- /dev/null +++ b/hydro/handler/notfound.js @@ -0,0 +1,19 @@ +const { Route, Handler } = require('../service/server'); +const { NotFoundError } = require('../error'); + +class NotFoundHandler extends Handler { + async prepare() { + throw new NotFoundError(); + } +} + +async function apply() { + Route('/:param1', module.exports.NotFoundHandler); + Route('/:param1/:param2', module.exports.NotFoundHandler); + Route('/:param1/:param2/:param3', module.exports.NotFoundHandler); + Route('/:param1/:param2/:param3/:param4', module.exports.NotFoundHandler); +} + +global.Hydro.handler.notfound = module.exports = { + NotFoundHandler, apply, +}; diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 91187c6ac..ccbad0e2b 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -71,7 +71,9 @@ 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, @@ -376,18 +378,39 @@ class ProblemCreateHandler extends Handler { } } -Route('/p', ProblemHandler); -Route('/problem/random', ProblemRandomHandler); -Route('/p/:pid', ProblemDetailHandler); -Route('/p/:pid/submit', ProblemSubmitHandler); -Route('/p/:pid/pretest', ProblemPretestHandler); -Route('/p/:pid/settings', ProblemSettingsHandler); -Route('/p/:pid/statistics', ProblemStatisticsHandler); -Route('/p/:pid/edit', ProblemEditHandler); -Route('/p/:pid/upload', ProblemDataUploadHandler); -Route('/p/:pid/data', ProblemDataDownloadHandler); -Route('/p/:pid/solution', ProblemSolutionHandler); -Route('/p/:pid/solution/:psid/raw', ProblemSolutionRawHandler); -Route('/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionReplyRawHandler); -Route('/problem/create', ProblemCreateHandler); -Connection('/p/:pid/pretest-conn', ProblemPretestConnectionHandler); +async function apply() { + Route('/p', module.exports.ProblemHandler); + Route('/problem/random', module.exports.ProblemRandomHandler); + Route('/p/:pid', module.exports.ProblemDetailHandler); + Route('/p/:pid/submit', module.exports.ProblemSubmitHandler); + Route('/p/:pid/pretest', module.exports.ProblemPretestHandler); + Route('/p/:pid/settings', module.exports.ProblemSettingsHandler); + Route('/p/:pid/statistics', module.exports.ProblemStatisticsHandler); + Route('/p/:pid/edit', module.exports.ProblemEditHandler); + Route('/p/:pid/upload', module.exports.ProblemDataUploadHandler); + Route('/p/:pid/data', module.exports.ProblemDataDownloadHandler); + Route('/p/:pid/solution', module.exports.ProblemSolutionHandler); + Route('/p/:pid/solution/:psid/raw', module.exports.ProblemSolutionRawHandler); + Route('/p/:pid/solution/:psid/:psrid/raw', module.exports.ProblemSolutionReplyRawHandler); + Route('/problem/create', module.exports.ProblemCreateHandler); + Connection('/p/:pid/pretest-conn', module.exports.ProblemPretestConnectionHandler); +} + +global.Hydro.handler.problem = module.exports = { + ProblemHandler, + ProblemRandomHandler, + ProblemDetailHandler, + ProblemSubmitHandler, + ProblemPretestHandler, + ProblemSettingsHandler, + ProblemStatisticsHandler, + ProblemEditHandler, + ProblemDataUploadHandler, + ProblemDataDownloadHandler, + ProblemSolutionHandler, + ProblemSolutionRawHandler, + ProblemSolutionReplyRawHandler, + ProblemCreateHandler, + ProblemPretestConnectionHandler, + apply, +}; diff --git a/hydro/handler/record.js b/hydro/handler/record.js index 1c6598974..488acc7d5 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -34,18 +34,16 @@ class RecordListHandler extends Handler { for (const pdoc of plist) { pdict[pdoc._id] = pdoc; } + const path = [ + ['Hydro', '/'], + ['record_main', null], + ]; this.response.body = { - path: [ - ['Hydro', '/'], - ['record_main', null], - ], - page, - rdocs, - pdict, - udict, + path, page, rdocs, pdict, udict, }; } } + class RecordDetailHandler extends Handler { async get({ rid }) { this.response.template = 'record_detail.html'; @@ -66,6 +64,7 @@ class RecordDetailHandler extends Handler { }; } } + class RecordRejudgeHandler extends Handler { async post({ rid }) { this.checkPerm(PERM_REJUDGE); @@ -77,6 +76,7 @@ class RecordRejudgeHandler extends Handler { this.back(); } } + class RecordConnectionHandler extends ConnectionHandler { async prepare() { bus.subscribe(['record_change'], this.onRecordChange); @@ -102,6 +102,7 @@ class RecordConnectionHandler extends ConnectionHandler { this.send({ html: await this.renderHTML('record_main_tr.html', { rdoc, udoc, pdoc }) }); } } + class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) { async prepare({ rid }) { const rdoc = await record.get(rid); @@ -131,8 +132,19 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti } } -Route('/r', RecordListHandler); -Route('/r/:rid', RecordDetailHandler); -Route('/r/:rid/rejudge', RecordRejudgeHandler); -Connection('/record-conn', RecordConnectionHandler); -Connection('/record-detail-conn', RecordDetailConnectionHandler); +async function apply() { + Route('/r', module.exports.RecordListHandler); + Route('/r/:rid', module.exports.RecordDetailHandler); + Route('/r/:rid/rejudge', module.exports.RecordRejudgeHandler); + Connection('/record-conn', module.exports.RecordConnectionHandler); + Connection('/record-detail-conn', module.exports.RecordDetailConnectionHandler); +} + +global.Hydro.handler.record = module.exports = { + RecordListHandler, + RecordDetailHandler, + RecordRejudgeHandler, + RecordConnectionHandler, + RecordDetailConnectionHandler, + apply, +}; diff --git a/hydro/handler/training.js b/hydro/handler/training.js index 8a5650e1b..568dbfccb 100644 --- a/hydro/handler/training.js +++ b/hydro/handler/training.js @@ -237,7 +237,17 @@ class TrainingEditHandler extends TrainingHandler { } } -Route('/t', TrainingMainHandler); -Route('/t/:tid', TrainingDetailHandler); -Route('/t/:tid/edit', TrainingEditHandler); -Route('/training/create', TrainingCreateHandler); +async function apply() { + Route('/t', module.exports.TrainingMainHandler); + Route('/t/:tid', module.exports.TrainingDetailHandler); + Route('/t/:tid/edit', module.exports.TrainingEditHandler); + Route('/training/create', module.exports.TrainingCreateHandler); +} + +global.Hydro.handler.training = module.exports = { + TrainingMainHandler, + TrainingDetailHandler, + TrainingEditHandler, + TrainingCreateHandler, + apply, +}; diff --git a/hydro/handler/user.js b/hydro/handler/user.js index 22cddb993..d52a9235d 100644 --- a/hydro/handler/user.js +++ b/hydro/handler/user.js @@ -181,11 +181,25 @@ class UserSearchHandler extends Handler { } } -Route('/login', UserLoginHandler); -Route('/register', UserRegisterHandler); -Route('/register/:code', UserRegisterWithCodeHandler); -Route('/logout', UserLogoutHandler); -Route('/lostpass', UserLostPassHandler); -Route('/lostpass/:code', UserLostPassWithCodeHandler); -Route('/user/search', UserSearchHandler); -Route('/user/:uid', UserDetailHandler); +async function apply() { + Route('/login', module.exports.UserLoginHandler); + Route('/register', module.exports.UserRegisterHandler); + Route('/register/:code', module.exports.UserRegisterWithCodeHandler); + Route('/logout', module.exports.UserLogoutHandler); + Route('/lostpass', module.exports.UserLostPassHandler); + Route('/lostpass/:code', module.exports.UserLostPassWithCodeHandler); + Route('/user/search', module.exports.UserSearchHandler); + Route('/user/:uid', module.exports.UserDetailHandler); +} + +global.Hydro.handler.user = module.exports = { + UserLoginHandler, + UserRegisterHandler, + UserRegisterWithCodeHandler, + UserLogoutHandler, + UserLostPassHandler, + UserLostPassWithCodeHandler, + UserSearchHandler, + UserDetailHandler, + apply, +}; diff --git a/hydro/lib/loader.js b/hydro/lib/loader.js index b8004269b..6840ec43a 100644 --- a/hydro/lib/loader.js +++ b/hydro/lib/loader.js @@ -14,9 +14,15 @@ function exist(name) { return true; } -const superRequire = (name) => (__non_webpack_require__ - ? __non_webpack_require__(root(name)) - : require(root(name))); +const superRequire = (name) => { + let m; + try { + m = require(root(name)); // eslint-disable-line import/no-dynamic-require + } catch (e) { + m = __non_webpack_require__(root(name)); // eslint-disable-line no-undef + } + return m; +}; async function prepare() { installed = fs.readdirSync(root('.build/module')); @@ -26,8 +32,8 @@ async function handler() { for (const i of installed) { if (exist(`.build/module/${i}/handler.js`)) { superRequire(`.build/module/${i}/handler.js`); + console.log(`Handler init: ${i}`); } - console.log(`Handler init: ${i}`); } } @@ -35,8 +41,8 @@ async function model() { for (const i of installed) { if (exist(`.build/module/${i}/model.js`)) { superRequire(`.build/module/${i}/model.js`); + console.log(`Model init: ${i}`); } - console.log(`Model init: ${i}`); } } diff --git a/hydro/model/contest.js b/hydro/model/contest.js index cc329f782..6973f96f4 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -51,7 +51,11 @@ 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'); + 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)); @@ -84,7 +88,8 @@ function getStatus(tid, uid) { } async function getListStatus(uid, tids) { const r = {}; - for (const tid of tids) r[tid] = await getStatus(tid, uid); // eslint-disable-line no-await-in-loop + // eslint-disable-next-line no-await-in-loop + for (const tid of tids) r[tid] = await getStatus(tid, uid); return r; } async function attend(tid, uid) { diff --git a/hydro/model/discussion.js b/hydro/model/discussion.js index e8b876a41..6e487e482 100644 --- a/hydro/model/discussion.js +++ b/hydro/model/discussion.js @@ -65,7 +65,7 @@ function editReply(drid, content) { async function delReply(drid) { const drdoc = await getReply(drid); if (!drdoc) throw new DocumentNotFoundError(drid); - return await Promise.all([ + return await Promise.all([ // eslint-disable-line no-return-await collReply.deleteOne({ _id: drid }), coll.updateOne({ _id: drdoc.did }, { $inc: { nReply: -1 } }), ]); @@ -112,6 +112,7 @@ async function editTailReply(drid, drrid, content) { break; } } + // eslint-disable-next-line no-return-await return await collReply.findOneAndUpdate({ _id: drdoc._id }, { $set: { reply } }); } diff --git a/hydro/model/problem.js b/hydro/model/problem.js index 9fd3f07b0..16c91d173 100644 --- a/hydro/model/problem.js +++ b/hydro/model/problem.js @@ -157,7 +157,7 @@ async function setTestdata(_id, readStream) { f.once('error', reject); }); if (pdoc.data && typeof pdoc.data === 'object') gridfs.delete(this.pdoc.data); - return await edit(_id, { data: f.id }); + return await edit(_id, { data: f.id }); // eslint-disable-line no-return-await } module.exports = { diff --git a/hydro/model/solution.js b/hydro/model/solution.js index ca8c1e8cd..61788a870 100644 --- a/hydro/model/solution.js +++ b/hydro/model/solution.js @@ -28,13 +28,14 @@ async function get(psid) { } function getMany(query, sort, page, limit) { - return coll.find(query).sort(sort).skip((page - 1) * limit).limit(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 } }); + await coll.updateOne({ _id }, { $set: { content } }); const psdoc = await get(_id); if (!psdoc) throw new SolutionNotFoundError(_id); return psdoc; @@ -79,7 +80,8 @@ async function editReply(psid, psrid, content) { break; } } - return await coll.findOneAndUpdate({ _id: psdoc._id }, { $set: { reply } }); + // eslint-disable-next-line no-return-await + return await coll.updateOne({ _id: psdoc._id }, { $set: { reply } }); } function delReply(psid, psrid) { @@ -91,7 +93,7 @@ async function vote(psid, uid, value) { 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 } }); + await coll.updateOne({ _id: psid }, { $inc: { vote: value } }); pssdoc = await collStatus.findOne({ psid, uid }); const psdoc = await coll.findOne({ _id: psid }); return [psdoc, pssdoc]; diff --git a/hydro/model/training.js b/hydro/model/training.js index aee594c16..15ce84bdb 100644 --- a/hydro/model/training.js +++ b/hydro/model/training.js @@ -27,12 +27,12 @@ async function enroll(tid, uid) { } catch (e) { throw new TrainingAlreadyEnrollError(tid, uid); } - await coll.findOneAndUpdate({ _id: tid }, { $inc: { enroll: 1 } }); + await coll.updateOne({ _id: tid }, { $inc: { enroll: 1 } }); } async function setStatus(tid, uid, $set) { - await collStatus.findOneAndUpdate({ tid, uid }, { $set }); - return await collStatus.findOne({ tid, uid }); + await collStatus.updateOne({ tid, uid }, { $set }); + return await collStatus.findOne({ tid, uid }); // eslint-disable-line no-return-await } module.exports = { diff --git a/hydro/script/install.js b/hydro/script/install.js new file mode 100644 index 000000000..7c323f040 --- /dev/null +++ b/hydro/script/install.js @@ -0,0 +1,54 @@ +const { defaults } = require('lodash'); +const db = require('../service/db'); +const builtin = require('../model/builtin'); +const pwhash = require('../lib/pwhash'); +const { udoc } = require('../interface'); + +async function run() { + const collUser = db.collection('user'); + const collRole = db.collection('role'); + const collBlacklist = db.collection('blacklist'); + const collToken = db.collection('token'); + async function createUser() { + const salt = pwhash.salt(); + await collUser.insertMany([ + defaults({ + _id: 0, + uname: 'Hydro', + unameLower: 'hydro', + mail: 'hydro@hydro', + mailLower: 'hydro@hydro', + role: 'guest', + }, udoc), + defaults({ + _id: 1, + mail: 'guest@hydro', + mailLower: 'guest@hydro', + uname: 'Guest', + unameLower: 'guest', + role: 'guest', + }, udoc), + defaults({ + _id: -1, + mail: 'root@hydro', + mailLower: 'root@hydro', + uname: 'Root', + unameLower: 'root', + hash: pwhash.hash('rootroot', salt), + salt, + gravatar: 'root@hydro', + role: 'root', + }, udoc), + ]); + } + 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'); +} + +module.exports = { run }; diff --git a/hydro/script/uninstall.js b/hydro/script/uninstall.js new file mode 100644 index 000000000..0ad95f68f --- /dev/null +++ b/hydro/script/uninstall.js @@ -0,0 +1,8 @@ +const db = require('../service/db.js'); + +async function run() { + await db.dropDatabase(); + console.log('Dropped'); +} + +module.exports = { run }; diff --git a/hydro/service/server.js b/hydro/service/server.js index cb3d90498..8341ee157 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -17,7 +17,7 @@ const token = require('../model/token'); const opcount = require('../model/opcount'); const { UserNotFoundError, BlacklistedError, PermissionError, - NotFoundError, UserFacingError, ValidationError, + UserFacingError, ValidationError, } = require('../error'); const app = new Koa(); @@ -166,19 +166,13 @@ class Handler { } async renderBody() { - if (!this.response.redirect) { - if (!(this.response.body || this.response.template)) { - this.response.body = { error: new NotFoundError() }; - this.response.template = 'error.html'; - } - if (!this.preferJson) { - if (this.response.body || this.response.template) { - if (this.request.query.noTemplate || this.preferJson) return; - const templateName = this.request.query.template || this.response.template; - if (templateName) { - this.response.body = this.response.body || {}; - await this.render(templateName, this.response.body); - } + if (!this.response.redirect && !this.preferJson) { + if (this.response.body || this.response.template) { + if (this.request.query.noTemplate || this.preferJson) return; + const templateName = this.request.query.template || this.response.template; + if (templateName) { + this.response.body = this.response.body || {}; + await this.render(templateName, this.response.body); } } } diff --git a/package.json b/package.json index a87a193ba..fa40b3517 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,16 @@ "devDependencies": { "eslint": "^6.8.0", "eslint-config-airbnb-base": "^14.1.0", - "eslint-plugin-import": "^2.20.2" + "eslint-plugin-import": "^2.20.2", + "friendly-errors-webpack-plugin": "^1.7.0", + "webpack": "^4.42.1" }, "scripts": { "build": "node build/index.js", + "build:development": "node build/development.js", + "build:production": "node build/production.js", "pack": "pkg .", - "lint": "eslint hydro module --fix" + "lint": "eslint build hydro module --fix" }, "pkg": { "scripts": [ diff --git a/templates/partials/domain_edit_form.html b/templates/partials/domain_edit_form.html deleted file mode 100644 index 8b1378917..000000000 --- a/templates/partials/domain_edit_form.html +++ /dev/null @@ -1 +0,0 @@ -