diff --git a/.gitignore b/.gitignore index 14c1d85f..2635ab9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ config.yaml +config.json .build/ *.build .uibuild/ diff --git a/build/webpack.js b/build/webpack.js index 63b2ae09..3f16590e 100644 --- a/build/webpack.js +++ b/build/webpack.js @@ -23,8 +23,6 @@ const build = async (type) => { mode: type, entry: { development: root('hydro/development.js'), - install: root('hydro/install.js'), - uninstall: root('hydro/uninstall.js'), }, output: { filename: '[name].js', diff --git a/hydro/handler/contest.js b/hydro/handler/contest.js index 7e7bd97f..76f66622 100644 --- a/hydro/handler/contest.js +++ b/hydro/handler/contest.js @@ -3,12 +3,12 @@ const { 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 system = require('../model/system'); const { Route, Handler } = require('../service/server'); const ContestHandler = contest.ContestHandlerMixin(Handler); @@ -27,7 +27,7 @@ class ContestListHandler extends ContestHandler { qs = 'rule={0}'.format(rule); } // eslint-disable-next-line prefer-const - [tdocs, tpcount] = await paginate(tdocs, page, constants.CONTEST_PER_PAGE); + [tdocs, tpcount] = await paginate(tdocs, page, await system.get('CONTEST_PER_PAGE')); const tids = []; for (const tdoc of tdocs) tids.push(tdoc._id); const tsdict = await contest.getListStatus(this.user._id, tids); @@ -51,12 +51,14 @@ class ContestDetailHandler extends ContestHandler { attended; if (tsdoc) { attended = tsdoc.attend === 1; - for (const pdetail of tsdoc.journal || []) { psdict[pdetail.pid] = pdetail; } + for (const pdetail of tsdoc.journal || []) psdict[pdetail.pid] = pdetail; if (this.canShowRecord(this.tdoc)) { const q = []; for (const i in psdict) q.push(psdict[i].rid); rdict = await record.getList(q); - } else { for (const 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; const udict = await user.getList([this.tdoc.owner]); const path = [ diff --git a/hydro/handler/discussion.js b/hydro/handler/discussion.js index addd03f0..63bb48e2 100644 --- a/hydro/handler/discussion.js +++ b/hydro/handler/discussion.js @@ -1,6 +1,7 @@ const paginate = require('../lib/paginate'); const problem = require('../model/problem'); const contest = require('../model/contest'); +const system = require('../model/system'); const user = require('../model/user'); const discussion = require('../model/discussion'); const { @@ -9,7 +10,6 @@ const { const { DiscussionNodeNotFoundError, DiscussionNotFoundError, DocumentNotFoundError, } = require('../error'); -const { constants } = require('../options'); const { PERM_VIEW_DISCUSSION, PERM_EDIT_DISCUSSION, PERM_EDIT_DISCUSSION_REPLY, PERM_VIEW_PROBLEM_HIDDEN, PERM_DELETE_DISCUSSION, PERM_DELETE_DISCUSSION_REPLY, @@ -65,7 +65,7 @@ class DiscussionMainHandler extends DiscussionHandler { const [ddocs, dpcount] = await paginate( discussion.getMulti(), page, - constants.DISCUSSION_PER_PAGE, + await system.get('DISCUSSION_PER_PAGE'), ); const udict = await user.getList(ddocs.map((ddoc) => ddoc.owner)); const path = [ @@ -84,7 +84,7 @@ class DiscussionNodeHandler extends DiscussionHandler { const [ddocs, dpcount] = await paginate( discussion.getMulti({ type, docId }), page, - constants.DISCUSSION_PER_PAGE, + await system.get('DISCUSSION_PER_PAGE'), ); const udict = await user.getList(ddocs.map((ddoc) => ddoc.owner)); const path = [ @@ -142,7 +142,7 @@ class DiscussionDetailHandler extends DiscussionHandler { const [drdocs, pcount, drcount] = await paginate( discussion.getMultiReply(did), page, - constants.REPLY_PER_PAGE, + await system.get('REPLY_PER_PAGE'), ); const uids = drdocs.map((drdoc) => drdoc.owner); uids.push(this.ddoc.owner); diff --git a/hydro/handler/home.js b/hydro/handler/home.js index 0229060c..3577da95 100644 --- a/hydro/handler/home.js +++ b/hydro/handler/home.js @@ -2,7 +2,6 @@ const { VerifyPasswordError, UserAlreadyExistError, InvalidTokenError, NotFoundError, MessageNotFoundError, } = require('../error'); -const options = require('../options'); const bus = require('../service/bus'); const { Route, Connection, Handler, ConnectionHandler, @@ -11,6 +10,7 @@ const misc = require('../lib/misc'); const md5 = require('../lib/md5'); const contest = require('../model/contest'); const message = require('../model/message'); +const system = require('../model/system'); const user = require('../model/user'); const setting = require('../model/setting'); const discussion = require('../model/discussion'); @@ -20,13 +20,12 @@ const { PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION, PERM_LOGGEDIN, } = require('../permission'); -const { CONTESTS_ON_MAIN, TRAININGS_ON_MAIN, DISCUSSIONS_ON_MAIN } = require('../options').constants; class HomeHandler extends Handler { async contest() { if (this.user.hasPerm(PERM_VIEW_CONTEST)) { const tdocs = await contest.getMulti() - .limit(CONTESTS_ON_MAIN) + .limit(await system.get('CONTESTS_ON_MAIN')) .toArray(); const tsdict = await contest.getListStatus( this.user._id, tdocs.map((tdoc) => tdoc._id), @@ -40,7 +39,7 @@ class HomeHandler extends Handler { if (this.user.hasPerm(PERM_VIEW_TRAINING)) { const tdocs = await training.getMulti() .sort('_id', 1) - .limit(TRAININGS_ON_MAIN) + .limit(await system.get('TRAININGS_ON_MAIN')) .toArray(); const tsdict = await training.getListStatus( this.user._id, tdocs.map((tdoc) => tdoc._id), @@ -53,7 +52,7 @@ class HomeHandler extends Handler { async discussion() { if (this.user.hasPerm(PERM_VIEW_DISCUSSION)) { const ddocs = await discussion.getMulti() - .limit(DISCUSSIONS_ON_MAIN) + .limit(await system.get('DISCUSSIONS_ON_MAIN')) .toArray(); const vndict = await discussion.getListVnodes(ddocs, this); return [ddocs, vndict]; @@ -106,7 +105,7 @@ class HomeSecurityHandler extends Handler { if (udoc) throw new UserAlreadyExistError(mail); const [rid] = await token.add( token.TYPE_CHANGEMAIL, - options.changemail_token_expire_seconds, + await system.get('changemail_token_expire_seconds'), { uid: this.udoc._id, mail }, ); await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', { diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 8d811d23..491ce775 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -15,7 +15,6 @@ 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, @@ -38,7 +37,7 @@ class ProblemHandler extends Handler { const [pdocs, pcount] = await paginate( problem.getMulti(q).sort({ pid: 1 }), page, - constants.PROBLEM_PER_PAGE, + await system.get('PROBLEM_PER_PAGE'), ); if (this.user.hasPerm(PERM_LOGGEDIN)) { psdict = await problem.getListStatus(this.user._id, pdocs.map((pdoc) => pdoc._id)); @@ -252,7 +251,8 @@ class ProblemSolutionHandler extends ProblemDetailHandler { this.checkPerm(PERM_VIEW_PROBLEM_SOLUTION); const [psdocs, pcount, pscount] = await paginate( solution.getMulti(this.pdoc._id), - page, constants.SOLUTION_PER_PAGE, + page, + await system.get('SOLUTION_PER_PAGE'), ); const uids = [this.pdoc.owner]; const docids = []; diff --git a/hydro/handler/record.js b/hydro/handler/record.js index 58e9d24e..a422609b 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -1,4 +1,3 @@ -const { constants } = require('../options'); const { PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN, @@ -6,6 +5,7 @@ const { const problem = require('../model/problem'); const record = require('../model/record'); const contest = require('../model/contest'); +const system = require('../model/system'); const user = require('../model/user'); const bus = require('../service/bus'); const { @@ -16,7 +16,7 @@ class RecordListHandler extends Handler { async get({ page = 1 }) { this.response.template = 'record_main.html'; const q = {}; - const rdocs = await record.getMany(q, { _id: -1 }, page, constants.RECORD_PER_PAGE); + const rdocs = await record.getMany(q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); const pdict = {}; const udict = {}; const ulist = []; diff --git a/hydro/handler/training.js b/hydro/handler/training.js index 94a4cff1..bcfaf73d 100644 --- a/hydro/handler/training.js +++ b/hydro/handler/training.js @@ -4,12 +4,12 @@ const { PERM_LOGGEDIN, PERM_VIEW_TRAINING, PERM_VIEW_PROBLEM_HIDDEN, PERM_CREATE_TRAINING, PERM_EDIT_TRAINING, } = require('../permission'); -const { constants } = require('../options'); const paginate = require('../lib/paginate'); const problem = require('../model/problem'); const builtin = require('../model/builtin'); const training = require('../model/training'); const user = require('../model/user'); +const system = require('../model/system'); const { Route, Handler } = require('../service/server'); async function _parseDagJson(dag) { @@ -60,7 +60,7 @@ class TrainingMainHandler extends TrainingHandler { const [tdocs, tpcount] = await paginate( training.getMulti().sort('_id', 1), page, - constants.TRAINING_PER_PAGE, + await system.get('TRAINING_PER_PAGE'), ); const tids = new Set(); for (const tdoc of tdocs) tids.add(tdoc._id); diff --git a/hydro/handler/user.js b/hydro/handler/user.js index c47d44c7..cf082a41 100644 --- a/hydro/handler/user.js +++ b/hydro/handler/user.js @@ -4,7 +4,6 @@ const token = require('../model/token'); const system = require('../model/system'); const { sendMail } = require('../lib/mail'); const misc = require('../lib/misc'); -const options = require('../options'); const { PERM_REGISTER_USER, PERM_LOGGEDIN } = require('../permission'); const { UserAlreadyExistError, InvalidTokenError, VerifyPasswordError, @@ -58,10 +57,10 @@ class UserRegisterHandler extends Handler { this.limitRate('send_mail', 3600, 30); const t = await token.add( token.TYPE_REGISTRATION, - options.registration_token_expire_seconds, + await system.get('registration_token_expire_seconds'), { mail }, ); - if (options.smtp.user) { + if (await system.get('smtp.user')) { const m = await this.renderHTML('user_register_mail', { url: `/register/${t}` }); await sendMail(mail, 'Sign Up', 'user_register_mail', m); this.response.template = 'user_register_mail_sent.html'; @@ -100,21 +99,18 @@ class UserRegisterWithCodeHandler extends Handler { } class UserLostPassHandler extends Handler { - constructor(ctx) { - if (!options.smtp.user) throw new SystemError('Cannot send mail'); - super(ctx); - } - async get() { + if (!await system.get('smtp.user')) throw new SystemError('Cannot send mail'); this.response.template = 'user_lostpass.html'; } async post({ mail }) { + if (!await system.get('smtp.user')) throw new SystemError('Cannot send mail'); const udoc = await user.getByEmail(mail); if (!udoc) throw new UserNotFoundError(mail); const tid = await token.add( token.TYPE_LOSTPASS, - options.lostpass_token_expire_seconds, + await system.get('lostpass_token_expire_seconds'), { uid: udoc._id }, ); const m = await this.renderHTML('user_lostpass_mail', { url: `/lostpass/${tid}`, uname: udoc.uname }); @@ -142,16 +138,12 @@ class UserLostPassWithCodeHandler extends Handler { } class UserDetailHandler extends Handler { - constructor(ctx) { - super(ctx); - this.response.template = 'user_detail.html'; - } - async get({ uid }) { const isSelfProfile = this.user._id === uid; const udoc = await user.getById(uid); if (!udoc) throw new UserNotFoundError(uid); const sdoc = await token.getMostRecentSessionByUid(uid); + this.response.template = 'user_detail.html'; this.response.body = { isSelfProfile, udoc, sdoc }; } } diff --git a/hydro/install.js b/hydro/install.js deleted file mode 100644 index 4180457e..00000000 --- a/hydro/install.js +++ /dev/null @@ -1,66 +0,0 @@ -require('./utils'); -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}@`; - mongourl += `${options.db.host}:${options.db.port}/${options.db.name}`; - 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() { - 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 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) => { - console.error(e); - process.exit(1); -}); diff --git a/hydro/loader.js b/hydro/loader.js index ea5f0139..97c192d9 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -142,6 +142,41 @@ async function service() { } } +async function install() { + await Promise.all([lib(), locale(), template()]); + require('./service/setup'); +} + +async function installDb() { + const system = require('./model/system'); + const def = { + PROBLEM_PER_PAGE: 100, + RECORD_PER_PAGE: 100, + SOLUTION_PER_PAGE: 20, + CONTEST_PER_PAGE: 20, + TRAINING_PER_PAGE: 10, + DISCUSSION_PER_PAGE: 50, + REPLY_PER_PAGE: 50, + CONTESTS_ON_MAIN: 5, + TRAININGS_ON_MAIN: 5, + DISCUSSIONS_ON_MAIN: 20, + 'db.ver': 1, + 'listen.https': false, + 'listen.port': 8888, + 'session.keys': ['Hydro'], + 'session.secure': false, + 'session.saved_expire_seconds': 3600 * 24, + 'session.unsaved_expire_seconds': 600, + changemail_token_expire_seconds: 3600 * 24, + registration_token_expire_seconds: 600, + }; + const tasks = []; + for (const key in def) { + tasks.push(system.set(key, def[key])); + } + await Promise.all(tasks); +} + async function load() { ensureDir(path.resolve(os.tmpdir(), 'hydro')); global.Hydro = { @@ -153,12 +188,16 @@ async function load() { ui: {}, }; await preload(); - await template(); require('./lib/i18n'); require('./utils'); require('./error'); require('./permission'); - require('./options'); + try { + require('./options'); + } catch (e) { + await install(); + return; + } const bus = require('./service/bus'); await new Promise((resolve) => { const h = () => { @@ -175,20 +214,23 @@ async function load() { 'template', 'validator', 'nav', ]; for (const i of builtinLib) require(`./lib/${i}`); - await lib(); - await locale(); + await Promise.all([lib(), locale(), template()]); require('./service/gridfs'); const server = require('./service/server'); + await server.prepare(); await service(); const builtinModel = [ 'blacklist', 'builtin', 'contest', 'discussion', 'message', 'opcount', 'problem', 'record', 'setting', 'solution', - 'system', 'token', 'training', 'user', + 'token', 'training', 'user', ]; for (const i of builtinModel) { const m = require(`./model/${i}`); if (m.index) await m.index(); } + const system = require('./model/system'); + const dbVer = await system.get('db.ver'); + if (dbVer !== 1) await installDb(); const builtinHandler = [ 'home', 'problem', 'record', 'judge', 'user', 'contest', 'training', 'discussion', 'manage', 'import', @@ -204,7 +246,7 @@ async function load() { for (const i in global.Hydro.service) { if (global.Hydro.service[i].postInit) await global.Hydro.service[i].postInit(); } - server.start(); + await server.start(); } module.exports = { load, active }; diff --git a/hydro/model/blacklist.js b/hydro/model/blacklist.js index b6dad2f0..456a98ff 100644 --- a/hydro/model/blacklist.js +++ b/hydro/model/blacklist.js @@ -6,11 +6,19 @@ async function add(ip) { const expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000); return coll.findOneAndUpdate({ _id: ip }, { $set: { expireAt } }, { upsert: true }); } + function get(ip) { return coll.findOne({ _id: ip }); } + function del(ip) { return coll.deleteOne({ _id: ip }); } -module.exports = { add, get, del }; +function index() { + return coll.createIndex('expireAt', { expireAfterSeconds: 0 }); +} + +module.exports = { + add, get, del, index, +}; diff --git a/hydro/model/system.js b/hydro/model/system.js index da674bcc..580d6e3d 100644 --- a/hydro/model/system.js +++ b/hydro/model/system.js @@ -12,7 +12,7 @@ async function update(_id, operation, config) { return get(_id); } async function set(_id, value) { - await coll.findOneAndUpdate({ _id }, { value }, { upsert: true }); + await coll.findOneAndUpdate({ _id }, { $set: { value } }, { upsert: true }); return get(_id); } /** diff --git a/hydro/model/token.js b/hydro/model/token.js index 1c2b73d1..f8276fbc 100644 --- a/hydro/model/token.js +++ b/hydro/model/token.js @@ -3,12 +3,21 @@ const { ValidationError } = require('../error'); const coll = db.collection('token'); +function index() { + return Promise.all([ + coll.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true }), + coll.createIndex('expireAt', { expireAfterSeconds: 0 }), + ]); +} + module.exports = { TYPE_SESSION: 0, TYPE_CSRF_TOKEN: 1, TYPE_REGISTER: 2, TYPE_CHANGEMAIL: 3, + index, + /** * Add a token. * @param {number} tokenType type of the token. diff --git a/hydro/options.js b/hydro/options.js index 31097973..79211105 100644 --- a/hydro/options.js +++ b/hydro/options.js @@ -51,14 +51,10 @@ let options = { }, }; -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'); - const t = yaml.safeLoad(fs.readFileSync(f)); - options = defaultsDeep(t, options); -} catch (e) { - console.error('Cannot load config'); -} +let f = path.resolve(process.cwd(), 'config.json'); +if (!fs.existsSync(f)) f = path.resolve(os.homedir(), '.config', 'hydro', 'config.json'); +if (!fs.existsSync(f)) f = path.resolve('/config/config.json'); +const t = JSON.parse(fs.readFileSync(f)); +options = defaultsDeep(t, options); module.exports = options; diff --git a/hydro/script/install.js b/hydro/script/install.js index 7c323f04..18821372 100644 --- a/hydro/script/install.js +++ b/hydro/script/install.js @@ -4,50 +4,42 @@ const builtin = require('../model/builtin'); const pwhash = require('../lib/pwhash'); const { udoc } = require('../interface'); +const collUser = db.collection('user'); +const collRole = db.collection('role'); +const collSystem = db.collection('system'); + 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 }); + 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 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'); } diff --git a/hydro/service/db.js b/hydro/service/db.js index 61359006..e1010fae 100644 --- a/hydro/service/db.js +++ b/hydro/service/db.js @@ -18,4 +18,5 @@ Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: global.Hydro.service.db = module.exports = { collection: (c) => db.collection(c), + dropDatabase: () => db.dropDatabase(), }; diff --git a/hydro/service/server.js b/hydro/service/server.js index d9b9cd61..3a6ec257 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -8,10 +8,10 @@ const cache = require('koa-static-cache'); const sockjs = require('sockjs'); const http = require('http'); const https = require('https'); -const options = require('../options'); const validator = require('../lib/validator'); const template = require('../lib/template'); const user = require('../model/user'); +const system = require('../model/system'); const blacklist = require('../model/blacklist'); const token = require('../model/token'); const opcount = require('../model/opcount'); @@ -21,19 +21,24 @@ const { } = 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, -})); -app.use(Body({ - multipart: true, - formidable: { - maxFileSize: 256 * 1024 * 1024, - }, -})); +let server; const router = new Router(); +async function prepare() { + const useHttps = await system.get('listen.https'); + server = (useHttps ? https : http).createServer(app.callback()); + app.keys = await system.get('session.keys'); + app.use(cache(path.join(process.cwd(), '.uibuild'), { + maxAge: 365 * 24 * 60 * 60, + })); + app.use(Body({ + multipart: true, + formidable: { + maxFileSize: 256 * 1024 * 1024, + }, + })); +} + class Handler { /** * @param {import('koa').Context} ctx @@ -128,8 +133,8 @@ class Handler { this.now = new Date(); this._handler.sid = this.request.cookies.get('sid'); this._handler.save = this.request.cookies.get('save'); - if (this._handler.save) this._handler.expireSeconds = options.session.saved_expire_seconds; - else this._handler.expireSeconds = options.session.unsaved_expire_seconds; + if (this._handler.save) this._handler.expireSeconds = await system.get('session.saved_expire_seconds'); + else this._handler.expireSeconds = await system.get('session.unsaved_expire_seconds'); this.session = this._handler.sid ? await token.update( this._handler.sid, @@ -213,7 +218,7 @@ class Handler { }, ); } - const cookie = { secure: options.session.secure }; + const cookie = { secure: await system.get('session.secure') }; if (this._handler.save) { cookie.expires = this.session.expireAt; cookie.maxAge = this._handler.expireSeconds; @@ -354,6 +359,7 @@ class ConnectionHandler { if (!this.user) throw new UserNotFoundError(this.session.uid); } } + function Connection(prefix, RouteConnHandler) { const sock = sockjs.createServer({ prefix }); sock.on('connection', async (conn) => { @@ -389,14 +395,14 @@ function Connection(prefix, RouteConnHandler) { sock.installHandlers(server); } -function start() { +async function start() { app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); app.use(router.routes()).use(router.allowedMethods()); Route('*', Handler); - server.listen(options.listen.port); - console.log('Server listening at: %s', options.listen.port); + server.listen(await system.get('listen.port')); + console.log('Server listening at: %s', await system.get('listen.port')); } global.Hydro.service.server = module.exports = { - Handler, ConnectionHandler, Route, Connection, start, + Handler, ConnectionHandler, Route, Connection, prepare, start, }; diff --git a/hydro/service/setup.js b/hydro/service/setup.js new file mode 100644 index 00000000..28e78160 --- /dev/null +++ b/hydro/service/setup.js @@ -0,0 +1,73 @@ +const fs = require('fs'); +const path = require('path'); +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 http = require('http'); +const nunjucks = require('nunjucks'); + +function Loader() { } +Loader.prototype.getSource = function getSource(name) { + if (!global.Hydro.template[name]) throw new Error(`Cannot get template ${name}`); + return { + src: global.Hydro.template[name], + path: name, + }; +}; + +class Nunjucks extends nunjucks.Environment { + constructor() { + super(new Loader(), { autoescape: true, trimBlocks: true }); + this.addFilter('json', (self) => JSON.stringify(self), false); + this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); + } +} +const env = new Nunjucks(); +function render(name) { + return new Promise((resolve, reject) => { + env.render(name, { + typeof: (o) => typeof o, + static_url: (str) => `/${str}`, + handler: { renderTitle: (str) => str }, + _: (str) => str, + }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); +} +const app = new Koa(); +const server = http.createServer(app.callback()); +const router = new Router(); +app.keys = ['Hydro']; +app.use(cache(path.join(process.cwd(), '.uibuild'), { + maxAge: 365 * 24 * 60 * 60, +})); +app.use(Body({ + multipart: true, + formidable: { + maxFileSize: 256 * 1024 * 1024, + }, +})); + +router.get('/', async (ctx) => { + ctx.body = await render('setup.html'); + ctx.response.type = 'text/html'; +}); +router.post('/', async (ctx) => { + const { + host, port, name, username, password, + } = ctx.request.body; + fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({ + host, port, name, username, password, + })); + ctx.body = await render('setup_done.html'); + ctx.response.type = 'text/html'; +}); + +app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); +app.use(router.routes()).use(router.allowedMethods()); +server.listen(8888); +console.log('Server listening at: 8888'); diff --git a/templates/setup.html b/templates/setup.html new file mode 100644 index 00000000..85b8d397 --- /dev/null +++ b/templates/setup.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + {{ _('Setup') }} + + + +
+
+
+
+

{{ _('Setup') }}

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+ + + diff --git a/templates/setup_done.html b/templates/setup_done.html new file mode 100644 index 00000000..340a29dc --- /dev/null +++ b/templates/setup_done.html @@ -0,0 +1,32 @@ + + + + + + + + + + + {{ _('Setup') }} + + + +
+
+
+
+

{{ _('Setup') }}

+
+ {{ _('Done. Please restart the program.') }} +
+
+
+
+
+ + +