mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
优化安装流程
This commit is contained in:
1 parent
63d55cedf4
commit
508f5644c3
30 files changed
+273
-133
No files matched your search
+1
-3
@@ -1,7 +1,5 @@
|
|||||||
FROM mhart/alpine-node:slim-10
|
FROM mhart/alpine-node:slim-10
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD .uibuild .build /app/
|
ADD .build /app
|
||||||
RUN mkdir /config
|
|
||||||
VOLUME [ "/config" ]
|
|
||||||
EXPOSE 8888
|
EXPOSE 8888
|
||||||
CMD ["node", ".build/development.js"]
|
CMD ["node", ".build/development.js"]
|
||||||
+2
-5
@@ -56,14 +56,11 @@ async function build(type) {
|
|||||||
rmdir(root(`.build/module/${f}`));
|
rmdir(root(`.build/module/${f}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const modules = [
|
const modules = await fsp.readdir(root('.build/module'));
|
||||||
'builtin', 'contest-rule-homework', 'judger', 'migrate-vijos', 'module-pastebin',
|
|
||||||
'problem-import-syzoj', 'wiki',
|
|
||||||
];
|
|
||||||
const j = {};
|
const j = {};
|
||||||
for (const m of modules) {
|
for (const m of modules) {
|
||||||
try {
|
try {
|
||||||
const d = await fsp.readFile(root(`.build/module/${m}.hydro`));
|
const d = await fsp.readFile(root(`.build/module/${m}`));
|
||||||
j[m] = d.toString('base64');
|
j[m] = d.toString('base64');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Module pack failed: ${m}`);
|
console.error(`Module pack failed: ${m}`);
|
||||||
|
|||||||
+2
-2
@@ -163,9 +163,9 @@ class RoleAlreadyExistError extends ForbiddenError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ProblemNotFoundError extends NotFoundError {
|
class ProblemNotFoundError extends NotFoundError {
|
||||||
constructor(pid) {
|
constructor(domainId, pid) {
|
||||||
super('ProblemNotFoundError');
|
super('ProblemNotFoundError');
|
||||||
this.params = [pid];
|
this.params = [domainId, pid];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-22
@@ -1,7 +1,9 @@
|
|||||||
|
const { ObjectID } = require('bson');
|
||||||
const paginate = require('../lib/paginate');
|
const paginate = require('../lib/paginate');
|
||||||
const system = require('../model/system');
|
const system = require('../model/system');
|
||||||
const user = require('../model/user');
|
const user = require('../model/user');
|
||||||
const discussion = require('../model/discussion');
|
const discussion = require('../model/discussion');
|
||||||
|
const document = require('../model/document');
|
||||||
const { Route, Handler } = require('../service/server');
|
const { Route, Handler } = require('../service/server');
|
||||||
const { DiscussionNotFoundError, DocumentNotFoundError } = require('../error');
|
const { DiscussionNotFoundError, DocumentNotFoundError } = require('../error');
|
||||||
const {
|
const {
|
||||||
@@ -10,17 +12,24 @@ const {
|
|||||||
PERM_LOGGEDIN, PERM_CREATE_DISCUSSION, PERM_REPLY_DISCUSSION,
|
PERM_LOGGEDIN, PERM_CREATE_DISCUSSION, PERM_REPLY_DISCUSSION,
|
||||||
} = require('../permission');
|
} = require('../permission');
|
||||||
|
|
||||||
|
const typeMapper = {
|
||||||
|
problem: document.TYPE_PROBLEM,
|
||||||
|
contest: document.TYPE_CONTEST,
|
||||||
|
node: document.TYPE_DISCUSSION_NODE,
|
||||||
|
training: document.TYPE_TRAINING,
|
||||||
|
homework: document.TYPE_HOMEWORK,
|
||||||
|
};
|
||||||
|
|
||||||
class DiscussionHandler extends Handler {
|
class DiscussionHandler extends Handler {
|
||||||
async _prepare({
|
async _prepare({
|
||||||
domainId, type = 'node', docId, name, did, drid, drrid,
|
domainId, type, name, did, drid, drrid,
|
||||||
}) {
|
}) {
|
||||||
this.checkPerm(PERM_VIEW_DISCUSSION);
|
this.checkPerm(PERM_VIEW_DISCUSSION);
|
||||||
docId = docId || name;
|
|
||||||
if (did) {
|
if (did) {
|
||||||
this.ddoc = await discussion.get(domainId, did);
|
this.ddoc = await discussion.get(domainId, did);
|
||||||
if (!this.ddoc) throw new DiscussionNotFoundError(did);
|
if (!this.ddoc) throw new DiscussionNotFoundError(did);
|
||||||
type = this.ddoc.parentType;
|
type = discussion.typeDisplay[this.ddoc.parentType];
|
||||||
docId = this.ddoc.parentId;
|
name = this.ddoc.parentId;
|
||||||
if (drid) {
|
if (drid) {
|
||||||
this.drdoc = await discussion.getReply(domainId, drid, did);
|
this.drdoc = await discussion.getReply(domainId, drid, did);
|
||||||
if (!this.drdoc) throw new DiscussionNotFoundError(drid);
|
if (!this.drdoc) throw new DiscussionNotFoundError(drid);
|
||||||
@@ -37,7 +46,11 @@ class DiscussionHandler extends Handler {
|
|||||||
// TODO(twd2): do more visibility check eg. contest
|
// TODO(twd2): do more visibility check eg. contest
|
||||||
// TODO(twd2): exclude problem/contest discussions?
|
// TODO(twd2): exclude problem/contest discussions?
|
||||||
// TODO(iceboy): continuation based pagination.
|
// TODO(iceboy): continuation based pagination.
|
||||||
this.vnode = await discussion.getVnode(domainId, { parentType: type, parentId: docId }, this);
|
if (ObjectID.isValid(name)) name = new ObjectID(name);
|
||||||
|
this.vnode = await discussion.getVnode(domainId, {
|
||||||
|
parentType: typeMapper[type],
|
||||||
|
parentId: name,
|
||||||
|
}, this);
|
||||||
if (this.ddoc) {
|
if (this.ddoc) {
|
||||||
this.ddoc.parentType = this.ddoc.parentType || this.vnode.type;
|
this.ddoc.parentType = this.ddoc.parentType || this.vnode.type;
|
||||||
this.ddoc.parentId = this.ddoc.parentId || this.vnode.id;
|
this.ddoc.parentId = this.ddoc.parentId || this.vnode.id;
|
||||||
@@ -45,7 +58,7 @@ class DiscussionHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiscussionMainHandler extends DiscussionHandler {
|
class DiscussionMainHandler extends Handler {
|
||||||
async get({ domainId, page = 1 }) {
|
async get({ domainId, page = 1 }) {
|
||||||
const [ddocs, dpcount] = await paginate(
|
const [ddocs, dpcount] = await paginate(
|
||||||
discussion.getMulti(domainId),
|
discussion.getMulti(domainId),
|
||||||
@@ -57,20 +70,20 @@ class DiscussionMainHandler extends DiscussionHandler {
|
|||||||
['Hydro', '/'],
|
['Hydro', '/'],
|
||||||
['discussion_main', null],
|
['discussion_main', null],
|
||||||
];
|
];
|
||||||
|
const vndict = await discussion.getListVnodes(domainId, ddocs, this);
|
||||||
this.response.template = 'discussion_main_or_node.html';
|
this.response.template = 'discussion_main_or_node.html';
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
ddocs, dpcount, udict, page, page_name: 'discussion_main', vnode: {}, path,
|
ddocs, dpcount, udict, page, page_name: 'discussion_main', vndict, vnode: {}, path,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiscussionNodeHandler extends DiscussionHandler {
|
class DiscussionNodeHandler extends DiscussionHandler {
|
||||||
async get({
|
async get({
|
||||||
domainId, type = 'node', docId, name, page = 1,
|
domainId, type, name, page = 1,
|
||||||
}) {
|
}) {
|
||||||
docId = docId || name;
|
|
||||||
const [ddocs, dpcount] = await paginate(
|
const [ddocs, dpcount] = await paginate(
|
||||||
discussion.getMulti(domainId, { parentType: type, parentId: docId }),
|
discussion.getMulti(domainId, { parentType: typeMapper[type], parentId: name }),
|
||||||
page,
|
page,
|
||||||
await system.get('DISCUSSION_PER_PAGE'),
|
await system.get('DISCUSSION_PER_PAGE'),
|
||||||
);
|
);
|
||||||
@@ -80,6 +93,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
|
|||||||
['discussion_main', '/discuss'],
|
['discussion_main', '/discuss'],
|
||||||
[this.vnode.title, null, true],
|
[this.vnode.title, null, true],
|
||||||
];
|
];
|
||||||
|
const vndict = { [typeMapper[type]]: { [name]: this.vnode } };
|
||||||
this.response.template = 'discussion_main_or_node.html';
|
this.response.template = 'discussion_main_or_node.html';
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
ddocs,
|
ddocs,
|
||||||
@@ -87,6 +101,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
|
|||||||
udict,
|
udict,
|
||||||
path,
|
path,
|
||||||
page,
|
page,
|
||||||
|
vndict,
|
||||||
vnode: this.vnode,
|
vnode: this.vnode,
|
||||||
page_name: 'discussion_node',
|
page_name: 'discussion_node',
|
||||||
};
|
};
|
||||||
@@ -99,12 +114,11 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
|||||||
this.checkPerm(PERM_CREATE_DISCUSSION);
|
this.checkPerm(PERM_CREATE_DISCUSSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
async get({ type = 'node', docId, name }) {
|
async get({ type, name }) {
|
||||||
docId = docId || name;
|
|
||||||
const path = [
|
const path = [
|
||||||
['Hydro', '/'],
|
['Hydro', '/'],
|
||||||
['discussion_main', '/discuss'],
|
['discussion_main', '/discuss'],
|
||||||
[this.vnode.title, `/discuss/${type}/${docId}`, true],
|
[this.vnode.title, `/discuss/${type}/${name}`, true],
|
||||||
['discussion_create', null],
|
['discussion_create', null],
|
||||||
];
|
];
|
||||||
this.response.template = 'discussion_create.html';
|
this.response.template = 'discussion_create.html';
|
||||||
@@ -112,13 +126,13 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async post({
|
async post({
|
||||||
domainId, type = 'node', docId, name, title, content, highlight,
|
domainId, type, name, title, content, highlight,
|
||||||
}) {
|
}) {
|
||||||
docId = docId || name;
|
|
||||||
this.limitRate('add_discussion', 3600, 30);
|
this.limitRate('add_discussion', 3600, 30);
|
||||||
|
if (ObjectID.isValid(name)) name = new ObjectID(name);
|
||||||
if (highlight) this.checkPerm(PERM_HIGHLIGHT_DISCUSSION);
|
if (highlight) this.checkPerm(PERM_HIGHLIGHT_DISCUSSION);
|
||||||
const did = await discussion.add(
|
const did = await discussion.add(
|
||||||
domainId, type, docId, this.user._id,
|
domainId, typeMapper[type], name, this.user._id,
|
||||||
title, content, this.request.ip, highlight,
|
title, content, this.request.ip, highlight,
|
||||||
);
|
);
|
||||||
this.response.body = { did };
|
this.response.body = { did };
|
||||||
@@ -136,7 +150,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
|
|||||||
page,
|
page,
|
||||||
await system.get('REPLY_PER_PAGE'),
|
await system.get('REPLY_PER_PAGE'),
|
||||||
);
|
);
|
||||||
const uids = drdocs.map(domainId, (drdoc) => drdoc.owner);
|
const uids = drdocs.map((drdoc) => drdoc.owner);
|
||||||
uids.push(this.ddoc.owner);
|
uids.push(this.ddoc.owner);
|
||||||
for (const drdoc of drdocs) {
|
for (const drdoc of drdocs) {
|
||||||
if (drdoc.reply) {
|
if (drdoc.reply) {
|
||||||
@@ -149,7 +163,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
|
|||||||
const path = [
|
const path = [
|
||||||
['Hydro', '/'],
|
['Hydro', '/'],
|
||||||
['discussion_main', '/discuss'],
|
['discussion_main', '/discuss'],
|
||||||
[this.vnode.title, `/discuss/${this.ddoc.parentType}`, true],
|
[this.vnode.title, `/discuss/${discussion.typeDisplay[this.ddoc.parentType]}/${this.ddoc.parentId}`, true],
|
||||||
[this.ddoc.title, null, true],
|
[this.ddoc.title, null, true],
|
||||||
];
|
];
|
||||||
this.response.template = 'discussion_detail.html';
|
this.response.template = 'discussion_detail.html';
|
||||||
@@ -275,10 +289,8 @@ async function apply() {
|
|||||||
Route('/discuss/:did/raw', module.exports.DiscussionDetailRawHandler);
|
Route('/discuss/:did/raw', module.exports.DiscussionDetailRawHandler);
|
||||||
Route('/discuss/:did/:drid/raw', module.exports.DiscussionReplyRawHandler);
|
Route('/discuss/:did/:drid/raw', module.exports.DiscussionReplyRawHandler);
|
||||||
Route('/discuss/:did/:drid/:drrid/raw', module.exports.DiscussionTailReplyRawHandler);
|
Route('/discuss/:did/:drid/:drrid/raw', module.exports.DiscussionTailReplyRawHandler);
|
||||||
Route('/discuss/node/:name', module.exports.DiscussionNodeHandler);
|
Route('/discuss/:type/:name', module.exports.DiscussionNodeHandler);
|
||||||
Route('/discuss/node/:name/create', module.exports.DiscussionCreateHandler);
|
Route('/discuss/:type/:name/create', module.exports.DiscussionCreateHandler);
|
||||||
Route('/discuss/:type/:docId', module.exports.DiscussionNodeHandler);
|
|
||||||
Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.discussion = module.exports = {
|
global.Hydro.handler.discussion = module.exports = {
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ class HomeSecurityHandler extends Handler {
|
|||||||
|
|
||||||
async get() {
|
async get() {
|
||||||
// TODO(iceboy): pagination? or limit session count for uid?
|
// TODO(iceboy): pagination? or limit session count for uid?
|
||||||
// TODO(masnn): UA & IP
|
|
||||||
const sessions = await token.getSessionListByUid(this.user._id);
|
const sessions = await token.getSessionListByUid(this.user._id);
|
||||||
const parsed = [];
|
const parsed = [];
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
@@ -173,8 +172,13 @@ class HomeSettingsHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async post(args) {
|
async post(args) {
|
||||||
// FIXME validation
|
const $set = {};
|
||||||
await user.setById(args.domainId, this.user._id, args);
|
for (const key in args) {
|
||||||
|
if (setting.SETTINGS_BY_KEY[args]) {
|
||||||
|
$set[key] = args[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await user.setById(args.domainId, this.user._id, $set);
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-1
@@ -78,10 +78,21 @@ class SystemSettingHandler extends SystemHandler {
|
|||||||
if (typeof args[key] === 'object') {
|
if (typeof args[key] === 'object') {
|
||||||
const subtasks = [];
|
const subtasks = [];
|
||||||
for (const sub in args[key]) {
|
for (const sub in args[key]) {
|
||||||
|
const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`];
|
||||||
|
if (s) {
|
||||||
|
if (s.ui === 'number') args[key][sub] = Number(args[key][sub]);
|
||||||
|
console.log(s);
|
||||||
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
|
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
tasks.push(Promise.all(subtasks));
|
tasks.push(Promise.all(subtasks));
|
||||||
} else tasks.push(system.set(key, args[key]));
|
} else {
|
||||||
|
const s = setting.SYSTEM_SETTINGS_BY_KEY[key];
|
||||||
|
if (s) {
|
||||||
|
if (s.ui === 'number') args[key] = Number(args[key]);
|
||||||
|
tasks.push(system.set(key, args[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
this.back();
|
this.back();
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ class RecordListHandler extends RecordHandler {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE'));
|
const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE'));
|
||||||
|
console.log(rdocs);
|
||||||
const [udict, pdict] = await Promise.all([
|
const [udict, pdict] = await Promise.all([
|
||||||
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
|
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
|
||||||
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid)),
|
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false),
|
||||||
]);
|
]);
|
||||||
const path = [
|
const path = [
|
||||||
['Hydro', '/'],
|
['Hydro', '/'],
|
||||||
|
|||||||
+1
-1
@@ -22,6 +22,6 @@ Item('/homework', 'homework_main', 'homework', permission.PERM_VIEW_HOMEWORK);
|
|||||||
Item('/discuss', 'discussion_main', 'discussion', permission.PERM_VIEW_DISCUSSION);
|
Item('/discuss', 'discussion_main', 'discussion', permission.PERM_VIEW_DISCUSSION);
|
||||||
Item('/c', 'contest_main', 'contest', permission.PERM_VIEW_CONTEST);
|
Item('/c', 'contest_main', 'contest', permission.PERM_VIEW_CONTEST);
|
||||||
Item('/domain/dashboard', 'domain_dashboard', 'domain', permission.PERM_MANAGE);
|
Item('/domain/dashboard', 'domain_dashboard', 'domain', permission.PERM_MANAGE);
|
||||||
Item('/manage/dashboard', 'manage_dashboard', 'manage', null, (handler) => handler.user.priv === 1);
|
Item('/manage/dashboard', 'manage', 'manage', null, (handler) => handler.user.priv === 1);
|
||||||
|
|
||||||
global.Hydro.lib.nav = module.exports = Item;
|
global.Hydro.lib.nav = module.exports = Item;
|
||||||
@@ -242,7 +242,7 @@ async function edit(domainId, tid, $set, type = document.TYPE_CONTEST) {
|
|||||||
* @param {string} domainId
|
* @param {string} domainId
|
||||||
* @param {ObjectID} tid
|
* @param {ObjectID} tid
|
||||||
* @param {number} type
|
* @param {number} type
|
||||||
* @returns {Tdoc}
|
* @returns {Promise<Tdoc>}
|
||||||
*/
|
*/
|
||||||
async function get(domainId, tid, type = document.TYPE_CONTEST) {
|
async function get(domainId, tid, type = document.TYPE_CONTEST) {
|
||||||
let tdoc;
|
let tdoc;
|
||||||
|
|||||||
@@ -2,9 +2,18 @@ const { ObjectID } = require('bson');
|
|||||||
const problem = require('./problem');
|
const problem = require('./problem');
|
||||||
const document = require('./document');
|
const document = require('./document');
|
||||||
const contest = require('./contest');
|
const contest = require('./contest');
|
||||||
|
const training = require('./training');
|
||||||
const { PERM_VIEW_PROBLEM_HIDDEN } = require('../permission');
|
const { PERM_VIEW_PROBLEM_HIDDEN } = require('../permission');
|
||||||
const { DocumentNotFoundError } = require('../error');
|
const { DocumentNotFoundError } = require('../error');
|
||||||
|
|
||||||
|
const typeDisplay = {
|
||||||
|
[document.TYPE_PROBLEM]: 'problem',
|
||||||
|
[document.TYPE_CONTEST]: 'contest',
|
||||||
|
[document.TYPE_DISCUSSION_NODE]: 'node',
|
||||||
|
[document.TYPE_TRAINING]: 'training',
|
||||||
|
[document.TYPE_HOMEWORK]: 'homework',
|
||||||
|
};
|
||||||
|
|
||||||
function add(domainId, parentType, parentId, owner, title, content, ip = null, highlight = false) {
|
function add(domainId, parentType, parentId, owner, title, content, ip = null, highlight = false) {
|
||||||
return document.add(
|
return document.add(
|
||||||
domainId, content, owner, document.TYPE_DISCUSSION,
|
domainId, content, owner, document.TYPE_DISCUSSION,
|
||||||
@@ -115,9 +124,7 @@ function getStatus(domainId, did, uid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addNode(domainId, _id, category) {
|
function addNode(domainId, _id, category) {
|
||||||
return document.add(
|
return document.add(domainId, category, 1, document.TYPE_DISCUSSION_NODE, _id);
|
||||||
domainId, _id, 1, document.TYPE_DISCUSSION_NODE, null, null, null, { category },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNode(domainId, _id) {
|
function getNode(domainId, _id) {
|
||||||
@@ -125,17 +132,32 @@ function getNode(domainId, _id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getVnode(domainId, ddoc, handler) {
|
async function getVnode(domainId, ddoc, handler) {
|
||||||
if (ddoc.parentType === 'problem') {
|
if (ddoc.parentType === document.TYPE_PROBLEM) {
|
||||||
const pdoc = await problem.getById(domainId, ddoc.parentId);
|
const pdoc = await problem.getById(domainId, ddoc.parentId);
|
||||||
if (!pdoc) return null;
|
if (!pdoc) return null;
|
||||||
if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||||
return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId };
|
return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||||
} if (ddoc.parentType === 'contest') {
|
}
|
||||||
|
if (ddoc.parentType === document.TYPE_CONTEST) {
|
||||||
const tdoc = await contest.get(domainId, ddoc.parentId);
|
const tdoc = await contest.get(domainId, ddoc.parentId);
|
||||||
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||||
} if (ddoc.parentType === 'node') {
|
}
|
||||||
|
if (ddoc.parentType === document.TYPE_DISCUSSION_NODE) {
|
||||||
const ndoc = await getNode(domainId, ddoc.parentId);
|
const ndoc = await getNode(domainId, ddoc.parentId);
|
||||||
return { title: ndoc._id, type: ddoc.parentType, id: ddoc.parentId };
|
return {
|
||||||
|
...ndoc,
|
||||||
|
title: ddoc.parentId,
|
||||||
|
type: ddoc.parentType,
|
||||||
|
id: ddoc.parentId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (ddoc.parentType === document.TYPE_TRAINING) {
|
||||||
|
const tdoc = await training.get(domainId, ddoc.parentId);
|
||||||
|
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||||
|
}
|
||||||
|
if (ddoc.parentType === document.TYPE_HOMEWORK) {
|
||||||
|
const tdoc = await contest.get(domainId, ddoc.parentId, document.TYPE_HOMEWORK);
|
||||||
|
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
title: 'Missing Node',
|
title: 'Missing Node',
|
||||||
@@ -147,13 +169,15 @@ async function getVnode(domainId, ddoc, handler) {
|
|||||||
async function getListVnodes(domainId, ddocs, handler) {
|
async function getListVnodes(domainId, ddocs, handler) {
|
||||||
const res = {};
|
const res = {};
|
||||||
for (const ddoc of ddocs) {
|
for (const ddoc of ddocs) {
|
||||||
|
if (!res[ddoc.parentType]) res[ddoc.parentType] = {};
|
||||||
// FIXME no-await-in-loop
|
// FIXME no-await-in-loop
|
||||||
res[ddoc._id] = await getVnode(domainId, ddoc, handler); // eslint-disable-line no-await-in-loop
|
res[ddoc.parentType][ddoc.parentId] = await getVnode(domainId, ddoc, handler); // eslint-disable-line no-await-in-loop
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.model.discussion = module.exports = {
|
global.Hydro.model.discussion = module.exports = {
|
||||||
|
typeDisplay,
|
||||||
add,
|
add,
|
||||||
get,
|
get,
|
||||||
edit,
|
edit,
|
||||||
|
|||||||
+6
-2
@@ -30,8 +30,12 @@ function del(_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function inc(_id) {
|
async function inc(_id) {
|
||||||
const file = await coll.findOne({ _id });
|
const doc = await coll.findOneAndUpdate(
|
||||||
file.count++;
|
{ _id },
|
||||||
|
{ $inc: { count: 1 } },
|
||||||
|
{ returnOriginal: false },
|
||||||
|
);
|
||||||
|
return (doc.value || {}).count;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dec(_id) {
|
async function dec(_id) {
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ module.exports = {
|
|||||||
task: require('./task'),
|
task: require('./task'),
|
||||||
message: require('./message'),
|
message: require('./message'),
|
||||||
setting: require('./setting'),
|
setting: require('./setting'),
|
||||||
|
discussion: require('./discussion'),
|
||||||
file: require('./file'),
|
file: require('./file'),
|
||||||
};
|
};
|
||||||
@@ -45,7 +45,7 @@ async function get(domainId, pid, uid = null) {
|
|||||||
const pdoc = Number.isInteger(pid)
|
const pdoc = Number.isInteger(pid)
|
||||||
? await document.get(domainId, document.TYPE_PROBLEM, pid)
|
? await document.get(domainId, document.TYPE_PROBLEM, pid)
|
||||||
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
|
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
|
||||||
if (!pdoc) throw new ProblemNotFoundError(pid);
|
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
|
||||||
if (uid) {
|
if (uid) {
|
||||||
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
|
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
|
||||||
}
|
}
|
||||||
@@ -110,10 +110,15 @@ async function random(domainId, query) {
|
|||||||
} return null;
|
} return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getList(domainId, pids) {
|
async function getList(domainId, pids, doThrow = true) {
|
||||||
const r = {};
|
const r = {};
|
||||||
|
if (doThrow) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
for (const pid of pids) r[pid] = await get(domainId, pid);
|
for (const pid of pids) r[pid] = await get(domainId, pid);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
for (const pid of pids) r[pid] = await get(domainId, pid).catch((e) => e);
|
||||||
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-11
@@ -41,7 +41,7 @@ const ACCOUNT_SETTINGS = [
|
|||||||
const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS];
|
const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS];
|
||||||
const SETTINGS_BY_KEY = {};
|
const SETTINGS_BY_KEY = {};
|
||||||
|
|
||||||
for (const setting in SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
for (const setting of SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
||||||
|
|
||||||
const SYSTEM_SETTINGS = [
|
const SYSTEM_SETTINGS = [
|
||||||
Setting('setting_basic', 'user.register', null, false, 'checkbox', 'Allow register'),
|
Setting('setting_basic', 'user.register', null, false, 'checkbox', 'Allow register'),
|
||||||
@@ -49,19 +49,32 @@ const SYSTEM_SETTINGS = [
|
|||||||
Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'),
|
Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'),
|
||||||
Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password'),
|
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.host', null, null, 'text', 'SMTP Server Host'),
|
||||||
Setting('setting_smtp', 'smtp.port', null, 465, 'text', 'SMTP Server Port'),
|
Setting('setting_smtp', 'smtp.port', null, 465, 'number', 'SMTP Server Port'),
|
||||||
Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'),
|
Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'),
|
||||||
Setting('setting_smtp', 'smtp.secure', null, false, 'checkbox', 'SSL'),
|
Setting('setting_smtp', 'smtp.secure', null, false, 'checkbox', 'SSL'),
|
||||||
Setting('setting_server', 'listen.port', null, 8888, 'text', 'Server Port', ''),
|
Setting('setting_server', 'server.hostname', null, null, 'text', 'Server Hostname'),
|
||||||
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'text', 'Problems per Page'),
|
Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'),
|
||||||
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'text', 'Contests per Page'),
|
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
|
||||||
Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'text', 'Discussion per Page'),
|
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
|
||||||
Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'text', 'Record per Page'),
|
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'number', 'Problems per Page'),
|
||||||
Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'text', 'Solutions per Page'),
|
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'number', 'Contests per Page'),
|
||||||
Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'text', 'Training per Page'),
|
Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'number', 'Discussion per Page'),
|
||||||
Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'text', 'Reply per Page'),
|
Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'number', 'Record per Page'),
|
||||||
|
Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'number', 'Solutions per Page'),
|
||||||
|
Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'number', 'Training per Page'),
|
||||||
|
Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'number', 'Reply per Page'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SYSTEM_SETTINGS_BY_KEY = {};
|
||||||
|
|
||||||
|
for (const setting of SYSTEM_SETTINGS) SYSTEM_SETTINGS_BY_KEY[setting.key] = setting;
|
||||||
|
|
||||||
global.Hydro.model.setting = module.exports = {
|
global.Hydro.model.setting = module.exports = {
|
||||||
Setting, PREFERENCE_SETTINGS, ACCOUNT_SETTINGS, SETTINGS, SETTINGS_BY_KEY, SYSTEM_SETTINGS,
|
Setting,
|
||||||
|
PREFERENCE_SETTINGS,
|
||||||
|
ACCOUNT_SETTINGS,
|
||||||
|
SETTINGS,
|
||||||
|
SETTINGS_BY_KEY,
|
||||||
|
SYSTEM_SETTINGS,
|
||||||
|
SYSTEM_SETTINGS_BY_KEY,
|
||||||
};
|
};
|
||||||
@@ -19,7 +19,7 @@ const allowFail = async (func, ...args) => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function run(username, password) {
|
async function run({ username, password } = {}) {
|
||||||
const def = {
|
const def = {
|
||||||
PROBLEM_PER_PAGE: 100,
|
PROBLEM_PER_PAGE: 100,
|
||||||
RECORD_PER_PAGE: 100,
|
RECORD_PER_PAGE: 100,
|
||||||
@@ -39,7 +39,7 @@ async function run(username, password) {
|
|||||||
'smtp.pass': '',
|
'smtp.pass': '',
|
||||||
'smtp.secure': false,
|
'smtp.secure': false,
|
||||||
'db.ver': 1,
|
'db.ver': 1,
|
||||||
'listen.port': 8888,
|
'server.port': 8888,
|
||||||
'session.keys': ['Hydro'],
|
'session.keys': ['Hydro'],
|
||||||
'session.secure': false,
|
'session.secure': false,
|
||||||
'session.saved_expire_seconds': 3600 * 24,
|
'session.saved_expire_seconds': 3600 * 24,
|
||||||
@@ -49,7 +49,10 @@ async function run(username, password) {
|
|||||||
};
|
};
|
||||||
let tasks = [];
|
let tasks = [];
|
||||||
for (const key in def) {
|
for (const key in def) {
|
||||||
tasks.push(system.set(key, def[key]));
|
tasks.push(system.get(key).then((value) => {
|
||||||
|
if (!value) return system.set(key, def[key]);
|
||||||
|
return Promise.resolve();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
tasks = [
|
tasks = [
|
||||||
@@ -91,12 +94,6 @@ async function run(username, password) {
|
|||||||
}, { upsert: true }),
|
}, { upsert: true }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const category in builtin.DEFAULT_NODES) {
|
|
||||||
const nodes = builtin.DEFAULT_NODES[category];
|
|
||||||
for (const node of nodes) {
|
|
||||||
tasks.push(discussion.addNode(node.name, category));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -10,9 +10,9 @@ mongourl += `${options.host}:${options.port}/${options.name}`;
|
|||||||
*/
|
*/
|
||||||
let db = null;
|
let db = null;
|
||||||
Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true })
|
Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||||
.then((Database) => {
|
.then((Client) => {
|
||||||
db = Database.db(options.name);
|
db = Client.db(options.name);
|
||||||
db.Db = Database;
|
db.Client = Client;
|
||||||
bus.publish('system_database_connected', null);
|
bus.publish('system_database_connected', null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ class Handler {
|
|||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.request = {
|
this.request = {
|
||||||
|
host: ctx.request.host,
|
||||||
|
hostname: ctx.request.hostname,
|
||||||
ip: ctx.request.ip,
|
ip: ctx.request.ip,
|
||||||
headers: ctx.request.headers,
|
headers: ctx.request.headers,
|
||||||
cookies: ctx.cookies,
|
cookies: ctx.cookies,
|
||||||
@@ -193,8 +195,8 @@ class Handler {
|
|||||||
token.TYPE_SESSION,
|
token.TYPE_SESSION,
|
||||||
this._handler.expireSeconds,
|
this._handler.expireSeconds,
|
||||||
{
|
{
|
||||||
update_ip: this.request.ip,
|
updateIp: this.request.ip,
|
||||||
update_ua: this.request.headers['user-agent'] || '',
|
updateUa: this.request.headers['user-agent'] || '',
|
||||||
},
|
},
|
||||||
) : { uid: 1 };
|
) : { uid: 1 };
|
||||||
if (!this.session) this.session = { uid: 1 };
|
if (!this.session) this.session = { uid: 1 };
|
||||||
@@ -302,6 +304,9 @@ function Route(route, RouteHandler, permission = null) {
|
|||||||
const args = {
|
const args = {
|
||||||
...ctx.params, ...ctx.query, ...ctx.request.body, domainId: 'system',
|
...ctx.params, ...ctx.query, ...ctx.request.body, domainId: 'system',
|
||||||
};
|
};
|
||||||
|
if (h.request.host !== await system.get('server.host')) {
|
||||||
|
args.domainId = h.request.url.split('//')[1].split(`.${await system.get('server.host')}`)[0] || '';
|
||||||
|
}
|
||||||
|
|
||||||
if (h.___prepare) await h.___prepare(args);
|
if (h.___prepare) await h.___prepare(args);
|
||||||
if (permission) h.checkPerm(permission);
|
if (permission) h.checkPerm(permission);
|
||||||
@@ -437,8 +442,8 @@ async function start() {
|
|||||||
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
||||||
app.use(router.routes()).use(router.allowedMethods());
|
app.use(router.routes()).use(router.allowedMethods());
|
||||||
Route('*', Handler);
|
Route('*', Handler);
|
||||||
server.listen(await system.get('listen.port'));
|
server.listen(await system.get('server.port'));
|
||||||
console.log('Server listening at: %s', await system.get('listen.port'));
|
console.log('Server listening at: %s', await system.get('server.port'));
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.service.server = module.exports = {
|
global.Hydro.service.server = module.exports = {
|
||||||
|
|||||||
+23
-18
@@ -1,8 +1,8 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const Koa = require('koa');
|
const Koa = require('koa');
|
||||||
const morgan = require('koa-morgan');
|
|
||||||
const Body = require('koa-body');
|
const Body = require('koa-body');
|
||||||
const Router = require('koa-router');
|
const Router = require('koa-router');
|
||||||
const cache = require('koa-static-cache');
|
const cache = require('koa-static-cache');
|
||||||
@@ -19,20 +19,11 @@ class Loader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Nunjucks extends nunjucks.Environment {
|
const env = new nunjucks.Environment(new Loader(), { autoescape: true, trimBlocks: true });
|
||||||
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) {
|
function render(name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
env.render(name, {
|
env.render(name, {
|
||||||
typeof: (o) => typeof o,
|
|
||||||
static_url: (str) => `/${str}`,
|
|
||||||
handler: { renderTitle: (str) => str },
|
|
||||||
_: (str) => str,
|
_: (str) => str,
|
||||||
}, (err, res) => {
|
}, (err, res) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
@@ -40,15 +31,16 @@ function render(name) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
const app = new Koa();
|
const app = new Koa();
|
||||||
const server = http.createServer(app.callback());
|
const server = http.createServer(app.callback());
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
app.keys = ['Hydro'];
|
app.keys = ['Hydro'];
|
||||||
app.use(cache(path.join(process.cwd(), '.uibuild'), {
|
app.use(cache(path.join(os.tmpdir(), 'hydro', 'builtin'), {
|
||||||
maxAge: 365 * 24 * 60 * 60,
|
maxAge: 365 * 24 * 60 * 60,
|
||||||
}));
|
}));
|
||||||
app.use(Body({ formidable: { maxFileSize: 256 * 1024 * 1024 } }));
|
app.use(Body());
|
||||||
router.get('/', async (ctx) => {
|
router.get('/', async (ctx) => {
|
||||||
ctx.body = await render('setup.html');
|
ctx.body = await render('setup.html');
|
||||||
ctx.response.type = 'text/html';
|
ctx.response.type = 'text/html';
|
||||||
@@ -66,9 +58,23 @@ async function setup() {
|
|||||||
useNewUrlParser: true, useUnifiedTopology: true,
|
useNewUrlParser: true, useUnifiedTopology: true,
|
||||||
});
|
});
|
||||||
const db = Database.db(name);
|
const db = Database.db(name);
|
||||||
// Check Write Access
|
await Promise.all([
|
||||||
await db.collection('test').insertOne({ test: 'test' });
|
db.collection('system').updateOne(
|
||||||
await db.collection('test').deleteOne({ test: 'test' });
|
{ _id: 'server.host' },
|
||||||
|
{ $set: { value: ctx.request.host } },
|
||||||
|
{ upsert: true },
|
||||||
|
),
|
||||||
|
db.collection('system').updateOne(
|
||||||
|
{ _id: 'server.hostname' },
|
||||||
|
{ $set: { value: ctx.request.hostname } },
|
||||||
|
{ upsert: true },
|
||||||
|
),
|
||||||
|
db.collection('system').updateOne(
|
||||||
|
{ _id: 'server.url' },
|
||||||
|
{ $set: { value: ctx.request.href } },
|
||||||
|
{ upsert: true },
|
||||||
|
),
|
||||||
|
]);
|
||||||
fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({
|
fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({
|
||||||
host, port, name, username, password,
|
host, port, name, username, password,
|
||||||
}));
|
}));
|
||||||
@@ -79,7 +85,6 @@ async function setup() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
|
||||||
app.use(router.routes()).use(router.allowedMethods());
|
app.use(router.routes()).use(router.allowedMethods());
|
||||||
server.listen(8888);
|
server.listen(8888);
|
||||||
console.log('Server listening at: 8888');
|
console.log('Server listening at: 8888');
|
||||||
|
|||||||
+2
-1
@@ -307,7 +307,8 @@ Create Domain: 创建域
|
|||||||
Properties: 属性
|
Properties: 属性
|
||||||
Access Control: 访问控制
|
Access Control: 访问控制
|
||||||
domain_main: 首页
|
domain_main: 首页
|
||||||
manage: 管理域
|
manage: 管理
|
||||||
|
manage_domain: 管理域
|
||||||
manage_dashboard: 概况
|
manage_dashboard: 概况
|
||||||
manage_discussion: 讨论节点
|
manage_discussion: 讨论节点
|
||||||
manage_edit: 编辑域资料
|
manage_edit: 编辑域资料
|
||||||
|
|||||||
+2
-1
@@ -301,7 +301,8 @@ Create Domain: 創建域
|
|||||||
Properties: 屬性
|
Properties: 屬性
|
||||||
Access Control: 訪問控制
|
Access Control: 訪問控制
|
||||||
domain_main: 首頁
|
domain_main: 首頁
|
||||||
manage: 管理域
|
manage: 管理
|
||||||
|
manage_domain: 管理域
|
||||||
manage_dashboard: 概況
|
manage_dashboard: 概況
|
||||||
manage_edit: 編輯域資料
|
manage_edit: 編輯域資料
|
||||||
manage_join_applications: 加域申請
|
manage_join_applications: 加域申請
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const child = require('child_process');
|
||||||
|
|
||||||
|
const appPath = path.resolve(__dirname, '..', 'hydro', 'development.js');
|
||||||
|
|
||||||
|
const app = child.fork(appPath);
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
const { mongodb } = global.Hydro.nodeModules;
|
const { mongodb } = global.Hydro.nodeModules;
|
||||||
const dst = global.Hydro.service.db;
|
const dst = global.Hydro.service.db;
|
||||||
|
const { file, problem, discussion } = global.Hydro.model;
|
||||||
|
|
||||||
const map = {};
|
const map = {};
|
||||||
|
|
||||||
const pid = (id) => {
|
const pid = (id) => {
|
||||||
if (map[id.toString()]) return map[id.toString()];
|
if (map[id.toString()]) return map[id.toString()];
|
||||||
if (id.generationTime) return id;
|
|
||||||
id = id.toString();
|
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,6 +113,8 @@ const tasks = {
|
|||||||
res[mapper[key]] = doc[key];
|
res[mapper[key]] = doc[key];
|
||||||
} else if (typeof mapper[key] === 'object') {
|
} else if (typeof mapper[key] === 'object') {
|
||||||
res[mapper[key].field] = mapper[key].processer(doc[key]);
|
res[mapper[key].field] = mapper[key].processer(doc[key]);
|
||||||
|
} else if (mapper[key] === null) {
|
||||||
|
// Ignore this key
|
||||||
} else {
|
} else {
|
||||||
console.log('Unknown key:', key);
|
console.log('Unknown key:', key);
|
||||||
}
|
}
|
||||||
@@ -214,7 +215,7 @@ const tasks = {
|
|||||||
'fs.chunks': async (doc) => doc,
|
'fs.chunks': async (doc) => doc,
|
||||||
file: async (doc) => ({
|
file: async (doc) => ({
|
||||||
_id: doc._id,
|
_id: doc._id,
|
||||||
count: doc.metadata.link,
|
count: 0,
|
||||||
secret: doc.metadata.secret,
|
secret: doc.metadata.secret,
|
||||||
size: doc.length,
|
size: doc.length,
|
||||||
md5: doc.md5,
|
md5: doc.md5,
|
||||||
@@ -223,7 +224,7 @@ const tasks = {
|
|||||||
|
|
||||||
const cursor = {
|
const cursor = {
|
||||||
user: (s) => s.collection('user').find(),
|
user: (s) => s.collection('user').find(),
|
||||||
document: (s) => s.collection('document').find(),
|
document: (s) => s.collection('document').find({ doc_type: { $ne: 20 } }),
|
||||||
'document.status': (s) => s.collection('document.status').find(),
|
'document.status': (s) => s.collection('document.status').find(),
|
||||||
record: (s) => s.collection('record').find(),
|
record: (s) => s.collection('record').find(),
|
||||||
'fs.files': (s) => s.collection('fs.files').find(),
|
'fs.files': (s) => s.collection('fs.files').find(),
|
||||||
@@ -231,6 +232,29 @@ const cursor = {
|
|||||||
file: (s) => s.collection('fs.files').find(),
|
file: (s) => s.collection('fs.files').find(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function discussionNode(src, report) {
|
||||||
|
const count = await src.collection('document').find({ doc_type: 20 }).count();
|
||||||
|
await report({ progress: 1, message: `discussion.node: ${count}` });
|
||||||
|
const total = Math.floor(count / 5);
|
||||||
|
for (let i = 0; i <= total; i++) {
|
||||||
|
const docs = await src.collection('document')
|
||||||
|
.find({ doc_type: 20 }).skip(i * 5).limit(5)
|
||||||
|
.toArray();
|
||||||
|
for (const doc of docs) {
|
||||||
|
const t = [];
|
||||||
|
for (const item of doc.content || []) {
|
||||||
|
const category = item[0];
|
||||||
|
const nodes = item[1];
|
||||||
|
for (const node of nodes || []) {
|
||||||
|
t.push(discussion.addNode(doc.domain_id, node.name, category));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(t).catch((e) => e);
|
||||||
|
}
|
||||||
|
await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fixPid(report) {
|
async function fixPid(report) {
|
||||||
const count = await dst.collection('document').find({ docType: 10 }).count();
|
const count = await dst.collection('document').find({ docType: 10 }).count();
|
||||||
await report({ progress: 1, message: `Fix pid: ${count}` });
|
await report({ progress: 1, message: `Fix pid: ${count}` });
|
||||||
@@ -239,12 +263,28 @@ async function fixPid(report) {
|
|||||||
const docs = await dst.collection('document')
|
const docs = await dst.collection('document')
|
||||||
.find({ docType: 10 }).skip(i * 50).limit(50)
|
.find({ docType: 10 }).skip(i * 50).limit(50)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
const t = [];
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
|
t.push(
|
||||||
dst.collection('document').updateOne(
|
dst.collection('document').updateOne(
|
||||||
{ _id: doc._id },
|
{ _id: doc._id },
|
||||||
{ $set: { pid: doc.pid || doc.docId.toString() } },
|
{ $set: { pid: doc.pid || doc.docId.toString() } },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (doc.data && doc.data.generationTime) {
|
||||||
|
t.push(file.inc(doc.data));
|
||||||
|
} else if (doc.data) {
|
||||||
|
const pdoc = await problem.get(doc.data.domain, pid(doc.data.pid));
|
||||||
|
t.push(
|
||||||
|
file.inc(pdoc.data),
|
||||||
|
dst.collection('document').updateOne(
|
||||||
|
{ _id: doc._id },
|
||||||
|
{ $set: { data: pdoc.data } },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(t);
|
||||||
await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) });
|
await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,9 +313,23 @@ async function task(name, src, report) {
|
|||||||
if (d.domainId && d.docId && d.docType) {
|
if (d.domainId && d.docId && d.docType) {
|
||||||
const query = { domainId: d.domainId, docId: d.docId, docType: d.docType };
|
const query = { domainId: d.domainId, docId: d.docId, docType: d.docType };
|
||||||
if (d.uid) query.uid = d.uid;
|
if (d.uid) query.uid = d.uid;
|
||||||
res.push(dst.collection(name).updateOne(
|
res.push((async () => {
|
||||||
query, { $set: docWithoutDid }, { upsert: true },
|
const data = await dst.collection(name).findOne(query);
|
||||||
));
|
if (data) {
|
||||||
|
await dst.collection(name).updateOne(query, { $set: docWithoutId });
|
||||||
|
} else if (d._id) {
|
||||||
|
const dat = await dst.collection(name).findOne({ _id: d._id });
|
||||||
|
if (dat) {
|
||||||
|
await dst.collection(name).updateOne({
|
||||||
|
_id: d._id,
|
||||||
|
}, { $set: docWithoutId });
|
||||||
|
} else {
|
||||||
|
await dst.collection(name).insertOne(d);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await dst.collection(name).insertOne(d);
|
||||||
|
}
|
||||||
|
})());
|
||||||
} else if (d._id) {
|
} else if (d._id) {
|
||||||
res.push(dst.collection(name).updateOne({
|
res.push(dst.collection(name).updateOne({
|
||||||
_id: d._id,
|
_id: d._id,
|
||||||
@@ -290,7 +344,6 @@ async function task(name, src, report) {
|
|||||||
lastProgress = progress;
|
lastProgress = progress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await fixPid(report);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function migrateVijos({
|
async function migrateVijos({
|
||||||
@@ -325,6 +378,8 @@ async function migrateVijos({
|
|||||||
await dst.collection('user').deleteMany({ _id: { $nin: [0, 1] } });
|
await dst.collection('user').deleteMany({ _id: { $nin: [0, 1] } });
|
||||||
const d = ['user', 'document', 'document.status', 'record', 'file'];
|
const d = ['user', 'document', 'document.status', 'record', 'file'];
|
||||||
for (const i of d) await task(i, src, report);
|
for (const i of d) await task(i, src, report);
|
||||||
|
await fixPid(report);
|
||||||
|
await discussionNode(src, report);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
{# No Compression #}
|
||||||
{% macro form_begin(args) %}
|
{% macro form_begin(args) %}
|
||||||
{% set label = args.label %}
|
{% set label = args.label %}
|
||||||
{% set columns = args.columns %}
|
{% set columns = args.columns %}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% if badge %}
|
{% if badge %}
|
||||||
<a class="user-profile-badge v-center badge--lv{{ udoc['level']|default(0) }}" href="/u/{{ udoc['_id']|default(0) }}" title="LV {{ udoc['level']|default(0) }}: Top {{ model.builtin.LEVELS[udoc['level']|default(0)]|default('N/A') }}%">LV {{ udoc['level']|default(0) }}</a>
|
<a class="user-profile-badge v-center badge--lv{{ udoc['level']|default(0) }}" href="/u/{{ udoc['_id']|default(0) }}" title="LV {{ udoc['level']|default(0) }}: Top {{ model.builtin.LEVELS[udoc['level']|default(0)]|default('N/A') }}%">LV {{ udoc['level']|default(0) }}</a>
|
||||||
{% if modbadge and handler.hasPerm(perm.PERM_MOD_BADGE) %}
|
{% if modbadge and udoc.hasPerm(perm.PERM_MOD_BADGE) %}
|
||||||
<span class="user-profile-badge v-center badge--mod" title="Moderator">MOD</span>
|
<span class="user-profile-badge v-center badge--mod" title="Moderator">MOD</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -7,15 +7,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-3 columns">
|
<div class="medium-3 columns">
|
||||||
{% if vnode.parentType == 'problem' %}
|
{% if vnode.parentType == model.document.TYPE_PROBLEM %}
|
||||||
{% set pdoc = vnode %}
|
{% set pdoc = vnode %}
|
||||||
{% set owner_udoc = udict[vnode.owner] %}
|
{% set owner_udoc = udict[vnode.owner] %}
|
||||||
{% include "partials/problem_sidebar.html" %}
|
{% include "partials/problem_sidebar.html" %}
|
||||||
{% elif vnode.parentType == 'contest' %}
|
{% elif vnode.parentType == model.document.TYPE_CONTEST %}
|
||||||
{% set tdoc = vnode %}
|
{% set tdoc = vnode %}
|
||||||
{% set owner_udoc = udict[vnode.owner] %}
|
{% set owner_udoc = udict[vnode.owner] %}
|
||||||
{% include "partials/contest_sidebar.html" %}
|
{% include "partials/contest_sidebar.html" %}
|
||||||
{% elif vnode.parentType == 'node' %}
|
{% elif vnode.parentType == model.document.TYPE_DISCUSSION_NODE %}
|
||||||
<div class="section side">
|
<div class="section side">
|
||||||
{% if vnode.pic %}<div class="discussion-node__bg {{ vnode.pic }}"></div>{% endif %}
|
{% if vnode.pic %}<div class="discussion-node__bg {{ vnode.pic }}"></div>{% endif %}
|
||||||
<div class="section__header">
|
<div class="section__header">
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
{% if vnode %}
|
{% if vnode %}
|
||||||
{% if handler.hasPerm(perm.PERM_CREATE_DISCUSSION) %}
|
{% if handler.hasPerm(perm.PERM_CREATE_DISCUSSION) %}
|
||||||
<p><a href="/discuss/{{ vnode.parentType }}/{{ vnode.parentId }}" class="expanded primary button">{{ _('Create a Discussion') }}</a></p>
|
<p><a href="/discuss/{{ model.discussion.typeDisplay[vnode.parentType] }}/{{ vnode.parentId }}/create" class="expanded primary button">{{ _('Create a Discussion') }}</a></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
{% if not handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
||||||
<p><a href="javascript:showSignInDialog();" class="expanded primary button">{{ _('Login to Create a Discussion') }}</a></p>
|
<p><a href="javascript:showSignInDialog();" class="expanded primary button">{{ _('Login to Create a Discussion') }}</a></p>
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
{{ form.form_text({
|
||||||
|
label:'ID',
|
||||||
|
name:'id',
|
||||||
|
value:domain._id,
|
||||||
|
disabled:true,
|
||||||
|
required:true
|
||||||
|
}) }}
|
||||||
{{ form.form_text({
|
{{ form.form_text({
|
||||||
label:'Name',
|
label:'Name',
|
||||||
name:'name',
|
name:'name',
|
||||||
|
|||||||
@@ -6,18 +6,12 @@
|
|||||||
<h1>{{ _('Add Module') }}</h1>
|
<h1>{{ _('Add Module') }}</h1>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="medium-6 columns">
|
<div class="medium-12 columns">
|
||||||
<label>
|
<label>
|
||||||
{{ _('URL') }}
|
{{ _('URL') }}
|
||||||
<input name="url" type="text" class="textbox" autocomplete="off" data-autofocus>
|
<input name="url" type="text" class="textbox" autocomplete="off" data-autofocus>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-6 columns">
|
|
||||||
<label>
|
|
||||||
{{ _('ID (Optional)') }}
|
|
||||||
<input name="id" type="text" class="textbox" autocomplete="off">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,10 +49,9 @@
|
|||||||
<td class="col--size">{{ size(module.size) if module.size else '?' }}</td>
|
<td class="col--size">{{ size(module.size) if module.size else '?' }}</td>
|
||||||
<td class="col--version">{{ module.version|default('?') }}</td>
|
<td class="col--version">{{ module.version|default('?') }}</td>
|
||||||
<td class="col--action">
|
<td class="col--action">
|
||||||
<a class="typo-a" href="/manage/module/download/{{ module.filename }}">{{ _('Download') }}</a>
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" name="operation" value="delete">
|
<input type="hidden" name="operation" value="delete">
|
||||||
<input type="hidden" name="id" value="{{ module.id }}">
|
<input type="hidden" name="id" value="{{ module.filename }}">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<input type="submit" class="typo-a" value="{{ _('Delete') }}">
|
<input type="submit" class="typo-a" value="{{ _('Delete') }}">
|
||||||
|
|||||||
@@ -15,18 +15,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="media__body top">
|
<div class="media__body top">
|
||||||
<h1 class="discussion__title"><a href="/discuss/{{ ddoc._id }}#{{ handler.datetime_stamp(ddoc.updateAt) if ddoc.update_at else '' }}" data-emoji-enabled>{{ ddoc['title'] }}</a></h1>
|
<h1 class="discussion__title"><a href="/discuss/{{ ddoc._id }}#{{ ddoc.updateAt }}" data-emoji-enabled>{{ ddoc['title'] }}</a></h1>
|
||||||
<ul class="supplementary list">
|
<ul class="supplementary list">
|
||||||
<li>
|
<li>
|
||||||
<a class="discussion-node-tag" href="/discuss/{{ ddoc.parentType }}/{{ ddoc.parentId }}">
|
<a class="discussion-node-tag" href="/discuss/{{ model.discussion.typeDisplay[ddoc.parentType] }}/{{ ddoc.parentId }}">
|
||||||
{% if ddoc.parentType == 'node' %}
|
{% if ddoc.parentType == model.document.TYPE_PROBLEM %}
|
||||||
<span class="v-center icon icon-tag"></span>
|
<span class="v-center icon icon-tag"></span>
|
||||||
{% elif ddoc.parentType == 'contest' %}
|
{% elif ddoc.parentType == model.document.TYPE_CONTEST %}
|
||||||
<span class="v-center icon icon-award"></span>
|
<span class="v-center icon icon-award"></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="v-center icon icon-flag"></span>
|
<span class="v-center icon icon-flag"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ vndict[ddoc._id].title|default(_('(missing)')) }}
|
{{ vndict[ddoc.parentType][ddoc.parentId].title|default(_('(missing)')) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="section__body">
|
<div class="section__body">
|
||||||
{% for setting in family_settings %}
|
{% for setting in family_settings %}
|
||||||
{% if setting.ui == 'text' or setting.ui == 'password' %}
|
{% if setting.ui == 'text' or setting.ui == 'password' or setting.ui == 'number' %}
|
||||||
{{ form.form_text({
|
{{ form.form_text({
|
||||||
type:setting.ui,
|
type:setting.ui,
|
||||||
label:setting.name,
|
label:setting.name,
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||||
<meta name="msapplication-TileColor" content="#579e9a">
|
<meta name="msapplication-TileColor" content="#579e9a">
|
||||||
<meta name="theme-color" content="#56758f">
|
<meta name="theme-color" content="#56758f">
|
||||||
|
<link rel="stylesheet" media="all" href="/vj4.css">
|
||||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
|
||||||
<title>{{ _('Setup') }}</title>
|
<title>{{ _('Setup') }}</title>
|
||||||
<script>
|
<script>
|
||||||
var _htmlNode = document.documentElement;
|
var _htmlNode = document.documentElement;
|
||||||
@@ -31,25 +30,25 @@
|
|||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<label class="inverse material textbox">
|
<label class="inverse material textbox">
|
||||||
{{ _('Database Port') }}
|
{{ _('Database Port') }}
|
||||||
<input name="port" type="number" value="27017" autofocus>
|
<input name="port" type="number" value="27017">
|
||||||
</label>
|
</label>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<label class="inverse material textbox">
|
<label class="inverse material textbox">
|
||||||
{{ _('Database Name') }}
|
{{ _('Database Name') }}
|
||||||
<input name="name" type="text" value="hydro" autofocus>
|
<input name="name" type="text" value="hydro">
|
||||||
</label>
|
</label>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<label class="inverse material textbox">
|
<label class="inverse material textbox">
|
||||||
{{ _('Database Username') }}
|
{{ _('Database Username') }}
|
||||||
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}">
|
||||||
</label>
|
</label>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<label class="inverse material textbox">
|
<label class="inverse material textbox">
|
||||||
{{ _('Database Password') }}
|
{{ _('Database Password') }}
|
||||||
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}">
|
||||||
</label>
|
</label>
|
||||||
</div></div>
|
</div></div>
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
@@ -62,6 +61,5 @@
|
|||||||
</div></div>
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="{{ static_url('vj4.js') }}"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in new issue
Block a user