From 7d27e560187c61d5616a6540b91635c019b6a0da Mon Sep 17 00:00:00 2001 From: masnn Date: Tue, 26 May 2020 13:07:17 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A8=E8=AE=BA=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/handler/discussion.js | 34 +++++++++------ hydro/handler/fs.js | 2 + hydro/loader.js | 2 +- hydro/model/builtin.js | 61 +++++++++++++++++++++++++++ hydro/model/discussion.js | 9 +++- hydro/model/file.js | 20 +++++---- hydro/model/index.js | 1 + hydro/model/message.js | 4 +- hydro/model/opcount.js | 2 +- hydro/model/record.js | 2 +- hydro/model/solution.js | 2 +- hydro/model/system.js | 2 +- hydro/model/task.js | 2 +- hydro/model/token.js | 2 +- hydro/model/training.js | 2 +- hydro/model/user.js | 2 +- hydro/script/install.js | 11 ++++- module/contest-rule-homework/model.js | 3 +- module/migrate-vijos/script.js | 30 ++++++++++--- templates/discussion_detail.html | 8 ++-- 20 files changed, 154 insertions(+), 47 deletions(-) diff --git a/hydro/handler/discussion.js b/hydro/handler/discussion.js index 35c8a86c6..59641bf5e 100644 --- a/hydro/handler/discussion.js +++ b/hydro/handler/discussion.js @@ -1,6 +1,4 @@ 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'); @@ -8,20 +6,20 @@ const { Route, Handler, } = require('../service/server'); const { - DiscussionNodeNotFoundError, DiscussionNotFoundError, DocumentNotFoundError, + DiscussionNotFoundError, DocumentNotFoundError, } = require('../error'); const { PERM_VIEW_DISCUSSION, PERM_EDIT_DISCUSSION, PERM_EDIT_DISCUSSION_REPLY, - PERM_VIEW_PROBLEM_HIDDEN, PERM_DELETE_DISCUSSION, PERM_DELETE_DISCUSSION_REPLY, - PERM_HIGHLIGHT_DISCUSSION, PERM_LOGGEDIN, PERM_CREATE_DISCUSSION, - PERM_REPLY_DISCUSSION, + PERM_DELETE_DISCUSSION, PERM_DELETE_DISCUSSION_REPLY, PERM_HIGHLIGHT_DISCUSSION, + PERM_LOGGEDIN, PERM_CREATE_DISCUSSION, PERM_REPLY_DISCUSSION, } = require('../permission'); class DiscussionHandler extends Handler { async _prepare({ - type, docId, did, drid, drrid, + type = 'node', docId, name, did, drid, drrid, }) { this.checkPerm(PERM_VIEW_DISCUSSION); + docId = docId || name; if (did) { this.ddoc = await discussion.get(did); if (!this.ddoc) throw new DiscussionNotFoundError(did); @@ -44,8 +42,11 @@ class DiscussionHandler extends Handler { // TODO(twd2): exclude problem/contest discussions? // TODO(iceboy): continuation based pagination. 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; + if (this.ddoc) { + this.ddoc.parentType = this.ddoc.parentType || this.vnode.type; + this.ddoc.parentId = this.ddoc.parentId || this.vnode.id; + } + console.log(this.vnode); } } @@ -69,7 +70,10 @@ class DiscussionMainHandler extends DiscussionHandler { } class DiscussionNodeHandler extends DiscussionHandler { - async get({ type, docId, page = 1 }) { + async get({ + type = 'node', docId, name, page = 1, + }) { + docId = docId || name; const [ddocs, dpcount] = await paginate( discussion.getMulti({ type, docId }), page, @@ -100,7 +104,8 @@ class DiscussionCreateHandler extends DiscussionHandler { this.checkPerm(PERM_CREATE_DISCUSSION); } - async get({ type, docId }) { + async get({ type = 'node', docId, name }) { + docId = docId || name; const path = [ ['Hydro', '/'], ['discussion_main', '/discuss'], @@ -112,8 +117,9 @@ class DiscussionCreateHandler extends DiscussionHandler { } async post({ - type, docId, title, content, highlight, + type = 'node', docId, name, title, content, highlight, }) { + docId = docId || name; this.limitRate('add_discussion', 3600, 30); if (highlight) this.checkPerm(PERM_HIGHLIGHT_DISCUSSION); const did = await discussion.add( @@ -153,7 +159,7 @@ class DiscussionDetailHandler extends DiscussionHandler { ]; this.response.template = 'discussion_detail.html'; this.response.body = { - path, ddoc: this.ddoc, dsdoc, drdocs, page, pcount, drcount, udict, + path, ddoc: this.ddoc, dsdoc, drdocs, page, pcount, drcount, udict, vnode: this.vnode, }; } @@ -272,6 +278,8 @@ async function apply() { Route('/discuss/:did/raw', module.exports.DiscussionDetailRawHandler); Route('/discuss/:did/:drid/raw', module.exports.DiscussionReplyRawHandler); Route('/discuss/:did/:drid/:drrid/raw', module.exports.DiscussionTailReplyRawHandler); + Route('/discuss/node/:name', module.exports.DiscussionNodeHandler); + Route('/discuss/node/:name/create', module.exports.DiscussionCreateHandler); Route('/discuss/:type/:docId', module.exports.DiscussionNodeHandler); Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler); } diff --git a/hydro/handler/fs.js b/hydro/handler/fs.js index e64d51d35..9815ad93d 100644 --- a/hydro/handler/fs.js +++ b/hydro/handler/fs.js @@ -3,6 +3,7 @@ const { Route, Handler } = require('../service/server'); class FileDownloadHandler extends Handler { async get({ id, secret, name }) { + if (name) name = Buffer.from(name, 'base64').toString(); this.response.attachment(name || id); this.response.body = await file.get(id, secret); } @@ -10,6 +11,7 @@ class FileDownloadHandler extends Handler { async function apply() { Route('/fs/:id/:secret', module.exports.FileDownloadHandler); + Route('/fs/:id/:name/:secret', module.exports.FileDownloadHandler); } global.Hydro.handler.file = module.exports = { diff --git a/hydro/loader.js b/hydro/loader.js index 8170e777d..4be81476f 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -33,7 +33,7 @@ try { pending.push(m); } } catch (e) { - console.log(e); + // Ignore Missing } async function preload() { diff --git a/hydro/model/builtin.js b/hydro/model/builtin.js index 12bd3c1a6..eb2d45dcf 100644 --- a/hydro/model/builtin.js +++ b/hydro/model/builtin.js @@ -5,6 +5,66 @@ const BUILTIN_ROLES = [ { _id: 'default', perm: perm.PERM_DEFAULT, count: 0 }, { _id: 'root', perm: perm.PERM_ALL, count: 0 }, ]; + +const DEFAULT_NODES = { + 探索: [ + { pic: 'qa', name: '问答' }, + { pic: 'share', name: '分享' }, + { pic: 'solution', name: '题解' }, + ], + Vijos: [ + { pic: 'vijos', name: 'Vijos' }, + { pic: null, name: '团队' }, + { pic: null, name: '月赛' }, + { pic: 'advice', name: '建议' }, + ], + 数据结构: [ + { pic: null, name: '散列表' }, + { pic: null, name: '搜索树' }, + { pic: null, name: '栈和队列' }, + { pic: null, name: '图' }, + { pic: null, name: '堆' }, + ], + 算法: [ + { pic: null, name: '数论' }, + { pic: null, name: '几何' }, + { pic: null, name: '图论' }, + { pic: null, name: '网络流' }, + { pic: null, name: '动态规划' }, + { pic: null, name: '背包' }, + { pic: null, name: '排序' }, + { pic: null, name: '搜索' }, + { pic: null, name: '并查集' }, + { pic: null, name: '贪心' }, + { pic: null, name: '博弈论' }, + ], + 在线题库: [ + { pic: null, name: 'CodeForces' }, + { pic: null, name: 'TopCoder' }, + { pic: null, name: 'POJ' }, + { pic: null, name: 'BZOJ' }, + { pic: null, name: 'USACO' }, + { pic: null, name: 'RQNOJ' }, + { pic: null, name: 'UOJ' }, + { pic: null, name: 'LOJ' }, + { pic: null, name: '洛谷' }, + ], + 泛: [ + { pic: null, name: '数学' }, + { pic: null, name: '编程' }, + { pic: null, name: '数据库' }, + { pic: null, name: 'C' }, + { pic: null, name: 'C++' }, + { pic: null, name: 'Pascal' }, + { pic: null, name: 'Java' }, + { pic: null, name: 'PHP' }, + { pic: null, name: 'Python' }, + { pic: null, name: '游戏' }, + { pic: null, name: '保送' }, + { pic: null, name: 'ACM' }, + ], +}; + const CATEGORIES = { 动态规划: [ 'LCS', @@ -232,6 +292,7 @@ const STATUS_CODES = { global.Hydro.model.builtin = module.exports = { BUILTIN_ROLES, + DEFAULT_NODES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS, diff --git a/hydro/model/discussion.js b/hydro/model/discussion.js index 630847c2f..8472a8bea 100644 --- a/hydro/model/discussion.js +++ b/hydro/model/discussion.js @@ -3,7 +3,7 @@ const problem = require('./problem'); const contest = require('./contest'); const { PERM_VIEW_PROBLEM_HIDDEN } = require('../permission'); const { DocumentNotFoundError } = require('../error'); -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('discussion'); const collReply = db.collection('discussion.reply'); @@ -133,6 +133,10 @@ function getStatus(did, uid) { return collStatus.findOne({ did, uid }); } +function addNode(_id, category) { + return collNode.insertOne({ _id, category }); +} + function getNode(_id) { return collNode.findOne({ _id }); } @@ -148,7 +152,7 @@ async function getVnode(ddoc, handler) { return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId }; } if (ddoc.parentType === 'node') { const ndoc = await getNode(ddoc.parentId); - return { ...ndoc, type: ddoc.parentType, id: ddoc.parentId }; + return { title: ndoc._id, type: ddoc.parentType, id: ddoc.parentId }; } return { title: 'Missing Node', @@ -185,6 +189,7 @@ global.Hydro.model.discussion = module.exports = { delTailReply, setStar, getStatus, + addNode, getVnode, getListVnodes, }; diff --git a/hydro/model/file.js b/hydro/model/file.js index fc0ea140c..55809ff8c 100644 --- a/hydro/model/file.js +++ b/hydro/model/file.js @@ -5,6 +5,10 @@ const hash = require('../lib/hash.hydro'); const coll = db.collection('file'); +function _timestamp() { + return Math.floor(Number(new Date()) / 1000000); +} + async function add(stream, filename, meta = {}) { const file = await coll.insertOne(meta); const w = fs.openUploadStreamWithId(file.insertedId, filename); @@ -43,11 +47,11 @@ async function dec(_id) { async function get(_id, secret = null) { const file = await coll.findOne({ _id }); if (typeof secret !== 'undefined') { - const timestamp = Math.floor(Number(new Date())).toString(); - if (!( - hash.check(file.secret, timestamp, secret) - || hash.check(file.secret, timestamp - 1, secret))) { - throw new ForbiddenError(); + const timestamp = _timestamp(); + if (!hash.check(file.secret, timestamp.toString(), secret)) { + if (!hash.check(file.secret, timestamp.toString() - 1, secret)) { + throw new ForbiddenError(); + } } } return fs.openDownloadStream(_id); @@ -59,11 +63,11 @@ function getMeta(_id) { async function url(_id, name) { const file = await coll.findOne({ _id }); - const secret = hash.hash(file.secret, Math.floor(Number(new Date())).toString()); - if (name) return `/fs/${_id}/${secret}?name=${Buffer.from(name).toString('base64')}`; + const secret = hash.hash(file.secret, _timestamp().toString()); + if (name) return `/fs/${_id}/${Buffer.from(name).toString('base64')}/${secret}`; return `/fs/${_id}/${secret}`; } -global.Hydro.model.blacklist = module.exports = { +global.Hydro.model.file = module.exports = { add, get, del, inc, dec, getMeta, url, }; diff --git a/hydro/model/index.js b/hydro/model/index.js index 0765ec387..03ea6a372 100644 --- a/hydro/model/index.js +++ b/hydro/model/index.js @@ -13,4 +13,5 @@ module.exports = { task: require('./task'), message: require('./message'), setting: require('./setting'), + file: require('./file'), }; diff --git a/hydro/model/message.js b/hydro/model/message.js index 340ddbaf5..827243959 100644 --- a/hydro/model/message.js +++ b/hydro/model/message.js @@ -21,8 +21,8 @@ async function send(from, to, content) { $setOnInsert: { from: Math.min(from, to), to: Math.max(from, to), - fromUnread: false, - toUnread: true, + fromUnread: from > to, + toUnread: from < to, }, }, { upsert: true }); return await coll.findOne({ // eslint-disable-line no-return-await diff --git a/hydro/model/opcount.js b/hydro/model/opcount.js index 9ad650e25..f10546528 100644 --- a/hydro/model/opcount.js +++ b/hydro/model/opcount.js @@ -1,5 +1,5 @@ const { OpcountExceededError } = require('../error'); -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('opcount'); diff --git a/hydro/model/record.js b/hydro/model/record.js index d5b2c6feb..f60a23be9 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -4,7 +4,7 @@ const { STATUS_WAITING } = require('./builtin').STATUS; const task = require('./task'); const problem = require('./problem'); const { RecordNotFoundError } = require('../error'); -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('record'); diff --git a/hydro/model/solution.js b/hydro/model/solution.js index 94ad8148a..5182b0cc2 100644 --- a/hydro/model/solution.js +++ b/hydro/model/solution.js @@ -1,7 +1,7 @@ const { ObjectID } = require('bson'); const { SolutionNotFoundError } = require('../error'); const validator = require('../lib/validator'); -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('solution'); const collStatus = db.collection('solution.status'); diff --git a/hydro/model/system.js b/hydro/model/system.js index 6eca03b98..e93e502c9 100644 --- a/hydro/model/system.js +++ b/hydro/model/system.js @@ -1,4 +1,4 @@ -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('system'); diff --git a/hydro/model/task.js b/hydro/model/task.js index a1dbe70de..65c902151 100644 --- a/hydro/model/task.js +++ b/hydro/model/task.js @@ -1,4 +1,4 @@ -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('task'); diff --git a/hydro/model/token.js b/hydro/model/token.js index 4658b3598..86cac5e0d 100644 --- a/hydro/model/token.js +++ b/hydro/model/token.js @@ -1,4 +1,4 @@ -const db = require('../service/db.js'); +const db = require('../service/db'); const { ValidationError } = require('../error'); const coll = db.collection('token'); diff --git a/hydro/model/training.js b/hydro/model/training.js index e47f032dd..ce39d5864 100644 --- a/hydro/model/training.js +++ b/hydro/model/training.js @@ -1,7 +1,7 @@ const assert = require('assert'); const validator = require('../lib/validator'); const { ValidationError, TrainingNotFoundError, TrainingAlreadyEnrollError } = require('../error'); -const db = require('../service/db.js'); +const db = require('../service/db'); const coll = db.collection('traning'); const collStatus = db.collection('training.status'); diff --git a/hydro/model/user.js b/hydro/model/user.js index 724df650e..962050825 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -31,7 +31,7 @@ class USER { } checkPassword(password) { - const h = global.Hydro.lib[`hash.${this.hashType}`]; + const h = global.Hydro.lib[`hash.${this.hashType || 'hydro'}`]; if (!h) throw new Error('Unknown hash method'); return h.check(password, this.salt(), this.hash()); } diff --git a/hydro/script/install.js b/hydro/script/install.js index 5a3aa761e..2a21b86f8 100644 --- a/hydro/script/install.js +++ b/hydro/script/install.js @@ -1,6 +1,7 @@ const { defaults } = require('lodash'); const db = require('../service/db'); const builtin = require('../model/builtin'); +const discussion = require('../model/discussion'); const system = require('../model/system'); const pwhash = require('../lib/hash.hydro'); const { udoc } = require('../interface'); @@ -36,7 +37,7 @@ async function run() { changemail_token_expire_seconds: 3600 * 24, registration_token_expire_seconds: 600, }; - const tasks = []; + let tasks = []; for (const key in def) { tasks.push(system.set(key, def[key])); } @@ -77,6 +78,14 @@ async function run() { } await collRole.deleteMany({ _id: { $in: builtin.BUILTIN_ROLES.map((role) => role._id) } }); await collRole.insertMany(builtin.BUILTIN_ROLES); + tasks = []; + 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); } global.Hydro.script.install = module.exports = { run }; diff --git a/module/contest-rule-homework/model.js b/module/contest-rule-homework/model.js index f1ceb4975..55963a3a2 100644 --- a/module/contest-rule-homework/model.js +++ b/module/contest-rule-homework/model.js @@ -1,4 +1,3 @@ const homework = require('./homework'); -const rules = global.Hydro['model.contest'].RULES; -rules.homework = homework; +global.Hydro.model.contest.RULES.homework = homework; diff --git a/module/migrate-vijos/script.js b/module/migrate-vijos/script.js index 52746ea3f..f95f4e556 100644 --- a/module/migrate-vijos/script.js +++ b/module/migrate-vijos/script.js @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ const { mongodb } = global.Hydro.nodeModules; const dst = global.Hydro.service.db; -const { problem } = global.Hydro.model; +const { problem, discussion } = global.Hydro.model; const map = {}; @@ -112,8 +112,8 @@ const tasks = { nReply: doc.num_replies, ip: doc.ip, updateAt: doc.update_at, - parentType: TYPE[doc.parentType], - parentId: doc.parentId, + parentType: TYPE[doc.parent_doc_type], + parentId: doc.parent_doc_id, }); } return discussions; @@ -123,8 +123,23 @@ const tasks = { owner: doc.owner_uid, ip: doc.ip, content: doc.content, - parentId: doc.parent_doc_id, + did: doc.parent_doc_id, })), + 'discussion.node': async (docs) => { + const t = []; + for (const doc of docs) { + if (doc.domain_id === 'system') { + for (const r of doc.content) { + const category = r[0]; + for (const n of r[1]) { + t.push(discussion.addNode(n.name, category)); + } + } + } + } + await Promise.all(t); + return []; + }, contest: async (docs) => { const RULES = { 2: 'oi', @@ -268,6 +283,7 @@ const cursor = { solution: (s) => s.collection('document').find({ doc_type: 11 }), discussion: (s) => s.collection('document').find({ doc_type: 21 }), 'discussion.reply': (s) => s.collection('document').find({ doc_type: 22 }), + 'discussion.node': (s) => s.collection('document').find({ doc_type: 20 }), contest: (s) => s.collection('document').find({ doc_type: 30 }), 'contest.status': (s) => s.collection('document.status').find({ doc_type: 30 }), training: (s) => s.collection('document').find({ doc_type: 40 }), @@ -322,15 +338,15 @@ async function migrateVijos({ } const d = [ 'problem', 'problem.status', 'solution', 'discussion', 'discussion.reply', - 'contest', 'training', 'training.status', 'record', 'file', + 'discussion.node', 'contest', 'training', 'training.status', 'record', 'file', ]; for (const i of d) { await dst.collection(i).deleteMany(); } const t = [ 'user', 'problem', 'problem.status', 'solution', 'discussion', - 'discussion.reply', 'contest', 'training', 'training.status', 'record', - 'file', + 'discussion.reply', 'discussion.node', 'contest', 'training', 'training.status', + 'record', 'file', ]; for (const i of t) await task(i, src, report); } diff --git a/templates/discussion_detail.html b/templates/discussion_detail.html index 328d89e7c..df9b602aa 100644 --- a/templates/discussion_detail.html +++ b/templates/discussion_detail.html @@ -117,9 +117,11 @@ - {% set pdoc=vnode %} - {% set owner_udoc=udict[vnode['owner']] %} - {% include "partials/problem_sidebar.html" %} + {% if vnode.type == 'problem' %} + {% set pdoc = vnode %} + {% set owner_udoc = udict[vnode['owner']] %} + {% include "partials/problem_sidebar.html" %} + {% endif %} {% endblock %}