mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
交互式安装
This commit is contained in:
1 parent
6134a16f8c
commit
27cfe81e31
21 files changed
+333
-181
No files matched your search
@@ -1,5 +1,6 @@
|
||||
node_modules/
|
||||
config.yaml
|
||||
config.json
|
||||
.build/
|
||||
*.build
|
||||
.uibuild/
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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);
|
||||
|
||||
+6
-14
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
+48
-6
@@ -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 };
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
+5
-9
@@ -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;
|
||||
+34
-42
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
+25
-19
@@ -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,
|
||||
};
|
||||
@@ -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');
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-page="setup" data-layout="immersive" class="layout--immersive page--setup nojs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="msapplication-TileColor" content="#579e9a">
|
||||
<meta name="theme-color" content="#56758f">
|
||||
|
||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
||||
<title>{{ _('Setup') }}</title>
|
||||
<script>
|
||||
var _htmlNode = document.documentElement;
|
||||
_htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="slideout-panel" id="panel">
|
||||
<div class="main">
|
||||
<div class="row"><div class="columns">
|
||||
<div class="immersive--content immersive--center">
|
||||
<h1>{{ _('Setup') }}</h1>
|
||||
<form method="POST">
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Host') }}
|
||||
<input name="host" type="text" value="127.0.0.1" autofocus>
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Port') }}
|
||||
<input name="port" type="number" value="27017" autofocus>
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Name') }}
|
||||
<input name="name" type="text" value="hydro" autofocus>
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Username') }}
|
||||
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Password') }}
|
||||
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<div class="text-center">
|
||||
<input type="submit" value="{{ _('Confirm') }}" class="inverse expanded rounded primary button">
|
||||
</div>
|
||||
</div></div>
|
||||
</form>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{ static_url('vj4.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-page="setup" data-layout="immersive" class="layout--immersive page--setup nojs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="msapplication-TileColor" content="#579e9a">
|
||||
<meta name="theme-color" content="#56758f">
|
||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
||||
<title>{{ _('Setup') }}</title>
|
||||
<script>
|
||||
var _htmlNode = document.documentElement;
|
||||
_htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="slideout-panel" id="panel">
|
||||
<div class="main">
|
||||
<div class="row"><div class="columns">
|
||||
<div class="immersive--content immersive--center">
|
||||
<h1>{{ _('Setup') }}</h1>
|
||||
<div class="text-center">
|
||||
{{ _('Done. Please restart the program.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{ static_url('vj4.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user