讨论节点

This commit is contained in:
masnn committed 2020-05-26 13:07:17 +08:00
1 parent 5e51bd2fe0
commit 7d27e56018
20 files changed
+154 -47

No files matched your search

+21 -13
View File
@@ -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);
}
+2
View File
@@ -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 = {
+1 -1
View File
@@ -33,7 +33,7 @@ try {
pending.push(m);
}
} catch (e) {
console.log(e);
// Ignore Missing
}
async function preload() {
+61
View File
@@ -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,
+7 -2
View File
@@ -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,
};
+12 -8
View File
@@ -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,
};
+1
View File
@@ -13,4 +13,5 @@ module.exports = {
task: require('./task'),
message: require('./message'),
setting: require('./setting'),
file: require('./file'),
};
+2 -2
View File
@@ -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
+1 -1
View File
@@ -1,5 +1,5 @@
const { OpcountExceededError } = require('../error');
const db = require('../service/db.js');
const db = require('../service/db');
const coll = db.collection('opcount');
+1 -1
View File
@@ -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');
+1 -1
View File
@@ -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');
+1 -1
View File
@@ -1,4 +1,4 @@
const db = require('../service/db.js');
const db = require('../service/db');
const coll = db.collection('system');
+1 -1
View File
@@ -1,4 +1,4 @@
const db = require('../service/db.js');
const db = require('../service/db');
const coll = db.collection('task');
+1 -1
View File
@@ -1,4 +1,4 @@
const db = require('../service/db.js');
const db = require('../service/db');
const { ValidationError } = require('../error');
const coll = db.collection('token');
+1 -1
View File
@@ -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');
+1 -1
View File
@@ -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());
}
+10 -1
View File
@@ -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 };
+1 -2
View File
@@ -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;
+23 -7
View File
@@ -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);
}
+5 -3
View File
@@ -117,9 +117,11 @@
</div>
</div>
</div>
{% 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 %}
</div>
</div>
{% endblock %}