diff --git a/build/index.js b/build/index.js index da6e21f39..692c3cd89 100644 --- a/build/index.js +++ b/build/index.js @@ -1,3 +1,4 @@ +/* eslint-disable no-await-in-loop */ const fs = require('fs'); const path = require('path'); const zlib = require('zlib'); @@ -5,6 +6,8 @@ const yaml = require('js-yaml'); const { root, ignoreFailure, rmdir } = require('./utils'); const template = require('./template'); +const fsp = fs.promises; + function getFiles(folder) { const res = []; const files = fs.readdirSync(root(folder)); @@ -48,11 +51,15 @@ async function build(type) { } const data = zlib.gzipSync(Buffer.from(yaml.safeDump(builtin)), { level: -1 }); fs.writeFileSync(root('.build/module/builtin.hydro'), data); + console.log('Build::Module'); await require('./buildModule')(type); + console.log('Build::Main'); await require('./webpack')(type); const t = fs.readdirSync(root('.build/module')); for (const f of t) { - if (fs.statSync(root(`.build/module/${f}`)).isDirectory()) rmdir(root(`.build/module/${f}`)); + if (fs.statSync(root(`.build/module/${f}`)).isDirectory()) { + rmdir(root(`.build/module/${f}`)); + } } const modules = [ 'builtin', 'contest-rule-homework', 'judger', 'migrate-vijos', 'module-pastebin', @@ -61,15 +68,16 @@ async function build(type) { const j = {}; for (const m of modules) { try { - const d = fs.readFileSync(root(`.build/module/${m}.hydro`)); + const d = await fsp.readFile(root(`.build/module/${m}.hydro`)); j[m] = d.toString('base64'); } catch (e) { console.error(`Module pack failed: ${m}`); } } - fs.writeFileSync(root('.build/module.json'), JSON.stringify(j)); + await fsp.writeFile(root('.build/module.json'), JSON.stringify(j)); + console.log('Build::Full'); await require('./webpack')(type, '-full'); - // fs.unlinkSync(root('.build/module.json')); + await fsp.unlink(root('.build/module.json')); } module.exports = build; diff --git a/hydro/handler/discussion.js b/hydro/handler/discussion.js index eb06e8df4..35c8a86c6 100644 --- a/hydro/handler/discussion.js +++ b/hydro/handler/discussion.js @@ -43,20 +43,9 @@ class DiscussionHandler extends Handler { // TODO(twd2): do more visibility check eg. contest // TODO(twd2): exclude problem/contest discussions? // TODO(iceboy): continuation based pagination. - if (type && docId) { - if (type === 'problem') { - const pdoc = await problem.getById(docId); - if (!pdoc) throw new DiscussionNodeNotFoundError(type, docId); - if (pdoc.hidden) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); - this.vnode = pdoc; - } else if (type === 'contest') { - const tdoc = await contest.get(docId); - if (!tdoc) throw new DiscussionNodeNotFoundError(type, docId); - this.vnode = tdoc; - } else throw new DiscussionNodeNotFoundError(type, docId); - this.vnode.parentType = type; - this.vnode.parentId = docId; - } + this.vnode = await discussion.getVnode({ parentType: type, parentId: docId }, this); + this.ddoc.parentType = this.ddoc.parentType || this.vnode.type; + this.ddoc.parentId = this.ddoc.parentId || this.vnode.id; } } diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 39cbdab18..da91486a3 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -186,9 +186,17 @@ class ManageSettingHandler extends ManageHandler { } async post(args) { + const tasks = []; for (const key in args) { - await system.set(key, args[key]); + if (typeof args[key] === 'object') { + const subtasks = []; + for (const sub in args[key]) { + 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(); } } diff --git a/hydro/lib/pwhash.js b/hydro/lib/hash.hydro.js similarity index 57% rename from hydro/lib/pwhash.js rename to hydro/lib/hash.hydro.js index d0a13c6d9..6a797906b 100644 --- a/hydro/lib/pwhash.js +++ b/hydro/lib/hash.hydro.js @@ -1,10 +1,6 @@ const crypto = require('crypto'); -module.exports = { - _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'), +global.Hydro.lib['hash.hydro'] = module.exports = { /** * @param {string} password * @param {string} salt @@ -21,7 +17,4 @@ module.exports = { const res = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256'); return res.toString('Hex'); }, - salt() { - return String.random(); - }, }; diff --git a/hydro/lib/mail.js b/hydro/lib/mail.js index fe6934caf..58eb7488a 100644 --- a/hydro/lib/mail.js +++ b/hydro/lib/mail.js @@ -1,43 +1,45 @@ const nodemailer = require('nodemailer'); const { SendMailError } = require('../error'); -module.exports = { - /** - * @param {string} to - * @param {string} subject - * @param {string} text - * @param {string} html - */ - async sendMail(to, subject, text, html) { - let t; - try { - const system = require('../model/system'); - const [host, port, secure, user, pass, from] = await Promise.all([ - system.get('smtp.host'), - system.get('smtp.port'), - system.get('smtp.secure'), - system.get('smtp.user'), - system.get('smtp.pass'), - system.get('smtp.from'), - ]); - const transporter = nodemailer.createTransport({ - host, port, secure, auth: { user, pass }, +/** + * @param {string} to + * @param {string} subject + * @param {string} text + * @param {string} html + */ +async function sendMail(to, subject, text, html) { + let t; + try { + const { system } = global.Hydro.model; + const [host, port, secure, user, pass, from] = await Promise.all([ + system.get('smtp.host'), + system.get('smtp.port'), + system.get('smtp.secure'), + system.get('smtp.user'), + system.get('smtp.pass'), + system.get('smtp.from'), + ]); + const transporter = nodemailer.createTransport({ + host, port, secure, auth: { user, pass }, + }); + t = await new Promise((resolve, reject) => { + transporter.sendMail({ + from, + to, + subject, + text, + html, + }, (err, info) => { + if (err) reject(err); + else resolve(info); }); - t = await new Promise((resolve, reject) => { - transporter.sendMail({ - from, - to, - subject, - text, - html, - }, (err, info) => { - if (err) reject(err); - else resolve(info); - }); - }); - } catch (e) { - throw new SendMailError(to); - } - return t; - }, + }); + } catch (e) { + throw new SendMailError(to); + } + return t; +} + +global.Hydro.lib.mail = module.exports = { + sendMail, }; diff --git a/hydro/lib/markdown.js b/hydro/lib/markdown.js index 5d25a2f16..28dbd3244 100644 --- a/hydro/lib/markdown.js +++ b/hydro/lib/markdown.js @@ -21,7 +21,8 @@ class Markdown extends MarkdownIt { this.use(katex); } } -module.exports = { + +global.Hydro.lib.markdown = module.exports = { unsafe: new Markdown(false), safe: new Markdown(true), }; diff --git a/hydro/lib/md5.js b/hydro/lib/md5.js index 59cdae888..e13e7e236 100644 --- a/hydro/lib/md5.js +++ b/hydro/lib/md5.js @@ -126,4 +126,5 @@ function rstr2hex(input) { } return output; } -module.exports = (s) => rstr2hex(rstrMD5(unescape(encodeURIComponent(s)))); + +global.Hydro.lib.md5 = module.exports = (s) => rstr2hex(rstrMD5(unescape(encodeURIComponent(s)))); diff --git a/hydro/lib/misc.js b/hydro/lib/misc.js index 9b55a9058..820eb1a28 100644 --- a/hydro/lib/misc.js +++ b/hydro/lib/misc.js @@ -1,10 +1,10 @@ const md5 = require('./md5'); -exports.gravatar = function gravatar(email, size = 32) { - return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size}`; -}; +function gravatar(email, s = 32) { + return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`; +} -exports.datetime_span = function datetimeSpan(dt, relative = true, format = '%Y-%m-%d %H:%M:%S') { +function datetimeSpan(dt, relative = true, format = '%Y-%m-%d %H:%M:%S') { if (!dt) return 'DATETIME_SPAN_ERROR'; if (dt.generationTime) dt = new Date(dt.generationTime * 1000); else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt); @@ -13,9 +13,9 @@ exports.datetime_span = function datetimeSpan(dt, relative = true, format = '%Y- dt.getTime() / 1000, dt.format(format), ); -}; +} -exports.paginate = function* paginate(page, numPages) { +function* paginate(page, numPages) { const radius = 2; let first; let last; if (page > 1) { @@ -25,7 +25,9 @@ exports.paginate = function* paginate(page, numPages) { 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]; + } 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]; @@ -34,15 +36,19 @@ exports.paginate = function* paginate(page, numPages) { if (last < numPages) yield ['ellipsis', 0]; if (page < numPages) yield ['next', page + 1]; yield ['last', numPages]; -}; +} -exports.size = function formatSize(size, base = 1) { - size *= base; +function size(s, base = 1) { + s *= base; 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; + if (s < unit) return '{0} {1}'.format(Math.round(s), unitName); + s /= unit; } - return '{0} {1}'.format(Math.round(size * unit), unitNames[unitNames.length - 1]); + return '{0} {1}'.format(Math.round(s * unit), unitNames[unitNames.length - 1]); +} + +global.Hydro.lib.misc = module.exports = { + gravatar, datetimeSpan, paginate, size, }; diff --git a/hydro/lib/paginate.js b/hydro/lib/paginate.js index 580c51cdb..be39a4bca 100644 --- a/hydro/lib/paginate.js +++ b/hydro/lib/paginate.js @@ -1,6 +1,6 @@ const { ValidationError } = require('../error'); -module.exports = async function paginate(cursor, page, pageSize) { +async function paginate(cursor, page, pageSize) { if (page <= 0) throw new ValidationError('page'); const [count, pageDocs] = await Promise.all([ cursor.count(), @@ -8,4 +8,6 @@ module.exports = async function paginate(cursor, page, pageSize) { ]); const numPages = Math.floor((count + pageSize - 1) / pageSize); return [pageDocs, numPages, count]; -}; +} + +global.Hydro.lib.paginate = module.exports = paginate; diff --git a/hydro/lib/template.js b/hydro/lib/template.js index 622b0c37b..4a93c77fb 100644 --- a/hydro/lib/template.js +++ b/hydro/lib/template.js @@ -28,26 +28,26 @@ class Nunjucks extends nunjucks.Environment { } const env = new Nunjucks(); -module.exports = { - render(name, state) { - Object.assign(state, { - typeof: (o) => typeof o, - static_url: (str) => `/${str}`, - datetime_span: misc.datetime_span, - paginate: misc.paginate, - perm, - status: builtin.status, - size: misc.size, - gravatar: misc.gravatar, - model, - Context: global.Hydro.ui, - isIE: (str) => str.includes('MSIE') || str.includes('rv:11.0'), +function render(name, state) { + Object.assign(state, { + typeof: (o) => typeof o, + static_url: (str) => `/${str}`, + datetimeSpan: misc.datetimeSpan, + paginate: misc.paginate, + perm, + status: builtin.status, + size: misc.size, + gravatar: misc.gravatar, + model, + Context: global.Hydro.ui, + isIE: (str) => str.includes('MSIE') || str.includes('rv:11.0'), + }); + return new Promise((resolve, reject) => { + env.render(name, state, (err, res) => { + if (err) reject(err); + else resolve(res); }); - return new Promise((resolve, reject) => { - env.render(name, state, (err, res) => { - if (err) reject(err); - else resolve(res); - }); - }); - }, -}; + }); +} + +global.Hydro.lib.template = module.exports = { render }; diff --git a/hydro/lib/validator.js b/hydro/lib/validator.js index 09f556728..2fc6b0508 100644 --- a/hydro/lib/validator.js +++ b/hydro/lib/validator.js @@ -29,7 +29,7 @@ const checkIntro = (s) => { if (!isIntro(s)) throw new ValidationError('intro'); const isDescription = () => true; const checkDescription = (s) => { if (!isDescription(s)) throw new ValidationError('description'); else return s; }; -module.exports = { +global.Hydro.lib.validator = module.exports = { isTitle, checkTitle, isUid, diff --git a/hydro/loader.js b/hydro/loader.js index 3121f5e36..f4ca238a4 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -25,12 +25,15 @@ try { // eslint-disable-next-line import/no-unresolved const f = require('../.build/module.json'); for (const filename in f) { - const m = { ...yaml.safeLoad(zlib.gunzipSync(f[filename])), filename, isBuiltin: true }; + const m = { + ...yaml.safeLoad(zlib.gunzipSync(Buffer.from(f[filename], 'base64'))), + filename, + isBuiltin: true, + }; pending.push(m); - console.log(filename); } } catch (e) { - // Builtin module is in the module directory + console.log(e); } async function preload() { @@ -225,7 +228,7 @@ async function load() { }); const builtinLib = [ 'axios', 'download', 'i18n', 'mail', 'markdown', - 'md5', 'misc', 'paginate', 'pwhash', 'rank', + 'md5', 'misc', 'paginate', 'hash.hydro', 'rank', 'template', 'validator', 'nav', ]; for (const i of builtinLib) require(`./lib/${i}`); diff --git a/hydro/model/blacklist.js b/hydro/model/blacklist.js index 456a98ff8..b3c503af9 100644 --- a/hydro/model/blacklist.js +++ b/hydro/model/blacklist.js @@ -19,6 +19,6 @@ function index() { return coll.createIndex('expireAt', { expireAfterSeconds: 0 }); } -module.exports = { +global.Hydro.model.blacklist = module.exports = { add, get, del, index, }; diff --git a/hydro/model/builtin.js b/hydro/model/builtin.js index 69538f32f..12bd3c1a6 100644 --- a/hydro/model/builtin.js +++ b/hydro/model/builtin.js @@ -229,7 +229,8 @@ const STATUS_CODES = { 22: 'progress', 30: 'ignored', }; -module.exports = { + +global.Hydro.model.builtin = module.exports = { BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, diff --git a/hydro/model/contest.js b/hydro/model/contest.js index 5605b03b4..cfdc5124c 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -342,7 +342,7 @@ const ContestHandlerMixin = (c) => class extends c { } }; -global.Hydro['model.contest'] = module.exports = { +global.Hydro.model.contest = module.exports = { RULES, ContestHandlerMixin, add, diff --git a/hydro/model/discussion.js b/hydro/model/discussion.js index e4ef3591e..630847c2f 100644 --- a/hydro/model/discussion.js +++ b/hydro/model/discussion.js @@ -142,15 +142,19 @@ async function getVnode(ddoc, handler) { const pdoc = await problem.getById(ddoc.parentId); if (!pdoc) return null; if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); - return { ...pdoc, parentType: ddoc.parentType, parentId: ddoc.parentId }; + return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId }; } if (ddoc.parentType === 'contest') { const tdoc = await contest.get(ddoc.parentId); - return { ...tdoc, parentType: ddoc.parentType, parentId: ddoc.parentId }; + return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId }; } if (ddoc.parentType === 'node') { const ndoc = await getNode(ddoc.parentId); - return { ...ndoc, parentType: ddoc.parentType, parentId: ddoc.parentId }; + return { ...ndoc, type: ddoc.parentType, id: ddoc.parentId }; } - return null; + return { + title: 'Missing Node', + type: 'Unknown', + id: new ObjectID(), + }; } async function getListVnodes(ddocs, handler) { @@ -162,7 +166,7 @@ async function getListVnodes(ddocs, handler) { return res; } -module.exports = { +global.Hydro.model.discussion = module.exports = { add, get, edit, diff --git a/hydro/model/opcount.js b/hydro/model/opcount.js index 916fc08c0..9ad650e25 100644 --- a/hydro/model/opcount.js +++ b/hydro/model/opcount.js @@ -3,26 +3,26 @@ 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 - */ - 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, - beginAt, - expireAt, - op: { $not: { $gte: maxOperations } }, - }, { $inc: { op: 1 } }, { upsert: true }); - } catch (e) { - throw new OpcountExceededError(op, periodSecs, maxOperations); - } - }, -}; +/** + * @param {string} op + * @param {string} ident + * @param {number} period_secs + * @param {number} max_operations + */ +async function 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, + beginAt, + expireAt, + op: { $not: { $gte: maxOperations } }, + }, { $inc: { op: 1 } }, { upsert: true }); + } catch (e) { + throw new OpcountExceededError(op, periodSecs, maxOperations); + } +} + +global.Hydro.model.opcount = module.exports = { inc }; diff --git a/hydro/model/record.js b/hydro/model/record.js index 809831ca1..d5b2c6feb 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -104,7 +104,7 @@ async function rejudge(rid) { }); } -module.exports = { +global.Hydro.model.record = module.exports = { add, get, getMany, diff --git a/hydro/model/setting.js b/hydro/model/setting.js index 6dec2a938..8345cf4cc 100644 --- a/hydro/model/setting.js +++ b/hydro/model/setting.js @@ -47,9 +47,17 @@ const SYSTEM_SETTINGS = [ Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'), Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password'), Setting('setting_smtp', 'smtp.host', null, null, 'text', 'SMTP Server Host'), - Setting('setting_smtp', 'smtp.port', null, null, 'text', 'SMTP Server Port'), + Setting('setting_smtp', 'smtp.port', null, 465, 'text', 'SMTP Server Port'), Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'), - Setting('setting_smtp', 'smtp.secure', null, null, 'checkbox', 'SSL'), + Setting('setting_smtp', 'smtp.secure', null, false, 'checkbox', 'SSL'), + Setting('setting_server', 'listen.port', null, 8888, 'text', 'Server Port', ''), + Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'text', 'Problems per Page'), + Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'text', 'Contests per Page'), + Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'text', 'Discussion per Page'), + Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'text', 'Record per Page'), + Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'text', 'Solutions per Page'), + Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'text', 'Training per Page'), + Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'text', 'Reply per Page'), ]; global.Hydro.model.setting = module.exports = { diff --git a/hydro/model/solution.js b/hydro/model/solution.js index 61788a870..94ad8148a 100644 --- a/hydro/model/solution.js +++ b/hydro/model/solution.js @@ -106,7 +106,7 @@ async function getListStatus(list, uid) { return result; } -module.exports = { +global.Hydro.model.solution = module.exports = { count, add, get, diff --git a/hydro/model/system.js b/hydro/model/system.js index 580d6e3d1..6eca03b98 100644 --- a/hydro/model/system.js +++ b/hydro/model/system.js @@ -22,7 +22,8 @@ async function set(_id, value) { function inc(field) { return update(field, { $inc: { value: 1 } }, { upsert: true }); } -module.exports = { + +global.Hydro.model.system = module.exports = { get, update, inc, diff --git a/hydro/model/token.js b/hydro/model/token.js index a0c364c6e..4658b3598 100644 --- a/hydro/model/token.js +++ b/hydro/model/token.js @@ -10,7 +10,7 @@ function index() { ]); } -module.exports = { +global.Hydro.model.token = module.exports = { TYPE_SESSION: 0, TYPE_CSRF_TOKEN: 1, TYPE_REGISTER: 2, diff --git a/hydro/model/training.js b/hydro/model/training.js index 782f0ab1b..e47f032dd 100644 --- a/hydro/model/training.js +++ b/hydro/model/training.js @@ -35,7 +35,7 @@ async function setStatus(tid, uid, $set) { return await collStatus.findOne({ tid, uid }); // eslint-disable-line no-return-await } -module.exports = { +global.Hydro.model.training = module.exports = { getPids(tdoc) { const pids = new Set(); for (const node of tdoc.dag) { diff --git a/hydro/model/user.js b/hydro/model/user.js index 0460a5dba..724df650e 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -1,7 +1,7 @@ const system = require('./system'); const { UserNotFoundError, UserAlreadyExistError } = require('../error'); const perm = require('../permission'); -const pwhash = require('../lib/pwhash'); +const pwhash = require('../lib/hash.hydro'); const db = require('../service/db'); const coll = db.collection('user'); @@ -12,8 +12,8 @@ class USER { this._id = user._id; this.mail = user.mail; this.uname = user.uname; - this.salt = user.salt; - this.hash = user.hash; + this.salt = () => user.salt; + this.hash = () => user.hash; this.perm = user.perm; this.viewLang = user.language || 'zh_CN'; this.codeLang = user.codeLang || 'c'; @@ -31,7 +31,9 @@ class USER { } checkPassword(password) { - return pwhash.check(password, this.salt, this.hash); + const h = global.Hydro.lib[`hash.${this.hashType}`]; + if (!h) throw new Error('Unknown hash method'); + return h.check(password, this.salt(), this.hash()); } } @@ -71,9 +73,9 @@ async function getByEmail(mail, ignoreMissing = false) { } function setPassword(uid, password) { - const salt = pwhash.salt(); + const salt = String.random(); return coll.findOneAndUpdate({ _id: uid }, { - $set: { salt, hash: pwhash.hash(password, salt) }, + $set: { salt, hash: pwhash.hash(password, salt), hashType: 'hydro' }, }); } @@ -88,11 +90,11 @@ function setEmail(uid, mail) { async function changePassword(uid, currentPassword, newPassword) { const udoc = await getById(uid); udoc.checkPassword(currentPassword); - const salt = pwhash.salt(); + const salt = String.random(); return await coll.findOneAndUpdate({ // eslint-disable-line no-return-await _id: udoc._id, }, { - $set: { salt, hash: pwhash.hash(newPassword, salt) }, + $set: { salt, hash: pwhash.hash(newPassword, salt), hashType: 'hydro' }, }); } @@ -106,7 +108,7 @@ async function inc(_id, field, n = 1) { async function create({ uid, mail, uname, password, regip = '127.0.0.1', role = 'default', }) { - const salt = pwhash.salt(); + const salt = String.random(); if (!uid) uid = system.inc('user'); try { await Promise.all([ @@ -118,6 +120,7 @@ async function create({ unameLower: uname.trim().toLowerCase(), password: pwhash.hash(password, salt), salt, + hashType: 'hydro', regat: new Date(), regip, loginat: new Date(), @@ -179,7 +182,7 @@ function index() { ]); } -module.exports = { +global.Hydro.model.user = module.exports = { changePassword, create, getByEmail, diff --git a/hydro/script/install.js b/hydro/script/install.js index a72293318..5a3aa761e 100644 --- a/hydro/script/install.js +++ b/hydro/script/install.js @@ -2,7 +2,7 @@ const { defaults } = require('lodash'); const db = require('../service/db'); const builtin = require('../model/builtin'); const system = require('../model/system'); -const pwhash = require('../lib/pwhash'); +const pwhash = require('../lib/hash.hydro'); const { udoc } = require('../interface'); const collUser = db.collection('user'); @@ -42,7 +42,7 @@ async function run() { } await Promise.all(tasks); try { - const salt = pwhash.salt(); + const salt = String.random(); await collUser.insertMany([ defaults({ _id: 0, @@ -79,4 +79,4 @@ async function run() { await collRole.insertMany(builtin.BUILTIN_ROLES); } -module.exports = { run }; +global.Hydro.script.install = module.exports = { run }; diff --git a/hydro/script/uninstall.js b/hydro/script/uninstall.js index 0ad95f68f..f4d80335e 100644 --- a/hydro/script/uninstall.js +++ b/hydro/script/uninstall.js @@ -5,4 +5,4 @@ async function run() { console.log('Dropped'); } -module.exports = { run }; +global.Hydro.script.uninstall = module.exports = { run }; diff --git a/hydro/service/bus.js b/hydro/service/bus.js index 410f00392..2ba23c308 100644 --- a/hydro/service/bus.js +++ b/hydro/service/bus.js @@ -1,7 +1,8 @@ const { EventEmitter } = require('events'); const bus = new EventEmitter(); -module.exports = { + +global.Hydro.service.bus = module.exports = { subscribe(events, handler) { for (const event of events) bus.on(event, handler); }, diff --git a/hydro/service/gridfs.js b/hydro/service/gridfs.js index 42faeb9dd..a08b419f4 100644 --- a/hydro/service/gridfs.js +++ b/hydro/service/gridfs.js @@ -2,4 +2,5 @@ const { GridFSBucket } = require('mongodb'); const db = require('./db'); if (!db.s) db.s = {}; -module.exports = new GridFSBucket(db); + +global.Hydro.service.gridfs = module.exports = new GridFSBucket(db); diff --git a/locales/en.yaml b/locales/en.yaml index ffa279c77..bae17ba28 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -57,6 +57,8 @@ manage_join_applications: Join Applications manage_role: System Role manage_user: System User manage_permission: System Permission +manage_module: Module Management +manage_setting: System Settings timeago_locale: en perm_general: General perm_problem: Problems diff --git a/locales/zh_CN.yaml b/locales/zh_CN.yaml index accb01cb5..87b70e021 100644 --- a/locales/zh_CN.yaml +++ b/locales/zh_CN.yaml @@ -316,6 +316,7 @@ manage_role: 管理角色 manage_user: 管理用户 manage_permission: 管理权限 manage_module: 管理模块 +manage_setting: 系统设置 domain_join: 加入域 'Join {0}': '加入 {0}' You are not allowed to join the domain. The link is either invalid or expired.: 您无法加入该域,链接无效或已过期。 diff --git a/templates/components/comments_discussion.html b/templates/components/comments_discussion.html index 942419b43..50c60f7e5 100644 --- a/templates/components/comments_discussion.html +++ b/templates/components/comments_discussion.html @@ -10,7 +10,7 @@
{{ user.render_inline(udoc, avatar=false) }} - @ {{ datetime_span(rdoc['_id'])|safe }} + @ {{ datetimeSpan(rdoc['_id'])|safe }}
{{ reply_operations(doc, rdoc) }}
@@ -113,7 +113,7 @@
{{ user.render_inline(udoc, avatar=false) }} - @ {{ datetime_span(doc['_id'])|safe }} + @ {{ datetimeSpan(doc['_id'])|safe }}
{% if handler.hasPerm(reply_post_perm) %} diff --git a/templates/components/comments_solution.html b/templates/components/comments_solution.html index c81392bf2..cd1482512 100644 --- a/templates/components/comments_solution.html +++ b/templates/components/comments_solution.html @@ -110,7 +110,7 @@
{{ user.render_inline(udoc) }} - @ {{ datetime_span(doc._id)|safe }} + @ {{ datetimeSpan(doc._id)|safe }}
{% if handler.hasPerm(reply_post_perm) %} @@ -142,7 +142,7 @@
- {{ user.render_inline(udoc, badge=false) }} @ {{ datetime_span(rdoc['_id'])|safe }} + {{ user.render_inline(udoc, badge=false) }} @ {{ datetimeSpan(rdoc['_id'])|safe }}
{% if handler.hasPerm(reply_post_perm) %} diff --git a/templates/components/contest.html b/templates/components/contest.html index 1f8234b71..cd46bf87b 100644 --- a/templates/components/contest.html +++ b/templates/components/contest.html @@ -1,6 +1,6 @@ {% import "components/user.html" as user with context %} {% macro render_time(tdoc_time) %} -{{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }} +{{ datetimeSpan(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }} {% endmacro %} {% macro render_duration(tdoc) %} diff --git a/templates/contest_detail.html b/templates/contest_detail.html index 9e0d6c483..6115e6329 100644 --- a/templates/contest_detail.html +++ b/templates/contest_detail.html @@ -51,7 +51,7 @@ {% else %} {{ _('Submitted') }} {% endif %} - {{ datetime_span(rdoc._id)|safe }} + {{ datetimeSpan(rdoc._id)|safe }} {% else %} {{ _('No Submissions') }} - diff --git a/templates/contest_main.html b/templates/contest_main.html index 010f309a3..494925440 100644 --- a/templates/contest_main.html +++ b/templates/contest_main.html @@ -83,8 +83,8 @@
-
{{ datetime_span(tdoc['beginAt'], false, '%d')|safe }}
-
{{ datetime_span(tdoc['beginAt'], false, '%Y-%m')|safe }}
+
{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}
+
{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}
diff --git a/templates/discussion_detail.html b/templates/discussion_detail.html index 30214a19a..328d89e7c 100644 --- a/templates/discussion_detail.html +++ b/templates/discussion_detail.html @@ -8,7 +8,7 @@
diff --git a/templates/home_security.html b/templates/home_security.html index 7808b1502..741966691 100644 --- a/templates/home_security.html +++ b/templates/home_security.html @@ -94,7 +94,7 @@
-

{{ _('Last Update At') }}: {{ datetime_span(session.updateAt)|safe }}

+

{{ _('Last Update At') }}: {{ datetimeSpan(session.updateAt)|safe }}

{{ _('Location') }}: {{ _(session.updateGeoip) }} ({{ session.updateIp }})

{{ _('Operating System') }}: {{ session.updateUa.os }}

{{ _('Browser') }}: {{ session.updateUa.browser }}

diff --git a/templates/main.html b/templates/main.html index 45fe6a152..fc14a63fc 100644 --- a/templates/main.html +++ b/templates/main.html @@ -27,8 +27,8 @@
-
{{ datetime_span(tdoc['beginAt'], false, '%d')|safe }}
-
{{ datetime_span(tdoc['beginAt'], false, '%Y-%m')|safe }}
+
{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}
+
{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}
diff --git a/templates/partials/discussion_list.html b/templates/partials/discussion_list.html index bfe624b2b..37763ee1f 100644 --- a/templates/partials/discussion_list.html +++ b/templates/partials/discussion_list.html @@ -32,7 +32,7 @@
  • {{ user.render_inline(udict[ddoc.owner], badge=false) }} @ - {{ datetime_span(ddoc.updateAt)|safe }} + {{ datetimeSpan(ddoc.updateAt)|safe }}
  • diff --git a/templates/problem_submit_tr.html b/templates/problem_submit_tr.html index d18461630..acb21a969 100644 --- a/templates/problem_submit_tr.html +++ b/templates/problem_submit_tr.html @@ -14,5 +14,5 @@ {{ (rdoc.time / 1000)|round(1) }}s - {{ datetime_span(rdoc['_id'].generationTime) }} + {{ datetimeSpan(rdoc['_id'].generationTime) }} diff --git a/templates/record_detail.html b/templates/record_detail.html index 6f6b19a44..66783ea84 100644 --- a/templates/record_detail.html +++ b/templates/record_detail.html @@ -81,11 +81,11 @@ {{ _('Submit At') }}
    - {{ datetime_span(rdoc._id)|safe }} + {{ datetimeSpan(rdoc._id)|safe }}
    {% if rdoc.judgeAt %}
    {{ _('Judged At') }}
    -
    {{ datetime_span(rdoc.judgeAt)|safe }}
    +
    {{ datetimeSpan(rdoc.judgeAt)|safe }}
    {% endif %} {% if judge_udoc %}
    {{ _('Judged By') }}
    diff --git a/templates/record_main_tr.html b/templates/record_main_tr.html index ba5acfd96..81b738791 100644 --- a/templates/record_main_tr.html +++ b/templates/record_main_tr.html @@ -26,5 +26,5 @@ {% if rdoc['status'] == status.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc.time }}ms {% if rdoc['status'] == status.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ size(rdoc.memory, 1024) }} {{ model.builtin.LANG_TEXTS[rdoc['lang']] }} - {{ datetime_span(rdoc['_id'])|safe }} + {{ datetimeSpan(rdoc['_id'])|safe }} diff --git a/templates/user_detail.html b/templates/user_detail.html index f373b08e2..cfa0750c5 100644 --- a/templates/user_detail.html +++ b/templates/user_detail.html @@ -17,10 +17,10 @@ ({{ _('UID') }}: {{ udoc['_id'] }})

    - {{ _('Registered at') }} {{ datetime_span(udoc['regat'])|safe }}, - {{ _('last login at') }} {{ datetime_span(udoc['loginat'])|safe }}, + {{ _('Registered at') }} {{ datetimeSpan(udoc['regat'])|safe }}, + {{ _('last login at') }} {{ datetimeSpan(udoc['loginat'])|safe }}, {% if sdoc %} - {{ _('last active at') }} {{ datetime_span(sdoc['updateAt'])|safe }}. + {{ _('last active at') }} {{ datetimeSpan(sdoc['updateAt'])|safe }}. {% else %} {{ _('currently offline') }}. {% endif %}