mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
core: strict discussion visiblility check (#537)
This commit is contained in:
1 parent
f39a28bfec
commit
b80c80d9b2
4 files changed
+44
-18
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { isSafeInteger } from 'lodash';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import {
|
||||
DiscussionLockedError, DiscussionNotFoundError, DocumentNotFoundError,
|
||||
DiscussionLockedError, DiscussionNodeNotFoundError, DiscussionNotFoundError, DocumentNotFoundError,
|
||||
PermissionError,
|
||||
} from '../error';
|
||||
import { DiscussionDoc, DiscussionReplyDoc, DiscussionTailReplyDoc } from '../interface';
|
||||
@@ -30,7 +30,7 @@ class DiscussionHandler extends Handler {
|
||||
vnode?: any;
|
||||
|
||||
@param('type', Types.Range(Object.keys(typeMapper)), true)
|
||||
@param('name', Types.Name, true)
|
||||
@param('name', Types.String, true)
|
||||
@param('did', Types.ObjectId, true)
|
||||
@param('drid', Types.ObjectId, true)
|
||||
@param('drrid', Types.ObjectId, true)
|
||||
@@ -55,15 +55,10 @@ class DiscussionHandler extends Handler {
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(twd2): do more visibility check eg. contest
|
||||
// TODO(twd2): exclude problem/contest discussions?
|
||||
// TODO(iceboy): continuation based pagination.
|
||||
this.vnode = await discussion.getVnode(domainId, typeMapper[type], name, this.user._id);
|
||||
if (this.vnode.assign?.length && !this.user.own(this.vnode)) {
|
||||
if (!Set.intersection(this.vnode.assign, this.user.group).size) {
|
||||
throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
|
||||
}
|
||||
}
|
||||
if (!discussion.checkVNodeVisibility(typeMapper[type], this.vnode, this.user)) throw new DiscussionNodeNotFoundError(this.vnode.id);
|
||||
if (this.ddoc) {
|
||||
this.ddoc.parentType ||= this.vnode.type;
|
||||
this.ddoc.parentId ||= this.vnode.id;
|
||||
@@ -73,11 +68,13 @@ class DiscussionHandler extends Handler {
|
||||
|
||||
class DiscussionMainHandler extends Handler {
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
@param('all', Types.Boolean)
|
||||
async get(domainId: string, page = 1, all = false) {
|
||||
// Limit to known types
|
||||
const parentType = { $in: Object.keys(typeMapper).map((i) => typeMapper[i]) };
|
||||
all &&= this.user.hasPerm(PERM.PERM_MOD_BADGE);
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId, { parentType }),
|
||||
discussion.getMulti(domainId, { parentType, ...all ? {} : { hidden: false } }),
|
||||
page,
|
||||
system.get('pagination.discussion'),
|
||||
);
|
||||
@@ -95,15 +92,16 @@ class DiscussionMainHandler extends Handler {
|
||||
|
||||
class DiscussionNodeHandler extends DiscussionHandler {
|
||||
@param('type', Types.Range(Object.keys(typeMapper)))
|
||||
@param('name', Types.Name)
|
||||
@param('name', Types.String)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, type: string, _name: string, page = 1) {
|
||||
let name: ObjectId | string | number;
|
||||
if (ObjectId.isValid(_name)) name = new ObjectId(_name);
|
||||
else if (isSafeInteger(parseInt(_name, 10))) name = parseInt(_name, 10);
|
||||
else name = _name;
|
||||
const hidden = this.user.own(this.vnode) || this.user.hasPerm(PERM.PERM_EDIT_DISCUSSION) ? {} : { hidden: false };
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId, { parentType: typeMapper[type], parentId: name }),
|
||||
discussion.getMulti(domainId, { parentType: typeMapper[type], parentId: name, ...hidden }),
|
||||
page,
|
||||
system.get('pagination.discussion'),
|
||||
);
|
||||
@@ -152,9 +150,10 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
||||
await this.limitRate('add_discussion', 3600, 60);
|
||||
if (highlight) this.checkPerm(PERM.PERM_HIGHLIGHT_DISCUSSION);
|
||||
if (pin) this.checkPerm(PERM.PERM_PIN_DISCUSSION);
|
||||
const hidden = this.vnode.hidden ?? false;
|
||||
const did = await discussion.add(
|
||||
domainId, typeMapper[type], this.vnode.id, this.user._id,
|
||||
title, content, this.request.ip, highlight, pin,
|
||||
title, content, this.request.ip, highlight, pin, hidden,
|
||||
);
|
||||
this.response.body = { did };
|
||||
this.response.redirect = this.url('discussion_detail', { did });
|
||||
@@ -397,9 +396,10 @@ class DiscussionEditHandler extends DiscussionHandler {
|
||||
else this.checkPerm(PERM.PERM_EDIT_DISCUSSION_SELF);
|
||||
if (!this.user.hasPerm(PERM.PERM_HIGHLIGHT_DISCUSSION)) highlight = this.ddoc.highlight;
|
||||
if (!this.user.hasPerm(PERM.PERM_PIN_DISCUSSION)) pin = this.ddoc.pin;
|
||||
const hidden = this.vnode.hidden ?? false;
|
||||
await Promise.all([
|
||||
discussion.edit(domainId, did, {
|
||||
title, highlight, pin, content, editor: this.user._id, edited: true,
|
||||
title, highlight, pin, content, editor: this.user._id, edited: true, hidden,
|
||||
}),
|
||||
oplog.log(this, 'discussion.edit', this.ddoc),
|
||||
]);
|
||||
|
||||
@@ -462,6 +462,7 @@ declare module './model/discussion' {
|
||||
sort: number;
|
||||
lastRCount: number;
|
||||
lock?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ import * as bus from '../service/bus';
|
||||
import db from '../service/db';
|
||||
import { NumberKeys } from '../typeutils';
|
||||
import { buildProjection } from '../utils';
|
||||
import { PERM } from './builtin';
|
||||
import * as contest from './contest';
|
||||
import * as document from './document';
|
||||
import problem from './problem';
|
||||
import * as training from './training';
|
||||
import { User } from './user';
|
||||
|
||||
export interface DiscussionDoc extends Document { }
|
||||
export type Field = keyof DiscussionDoc;
|
||||
@@ -20,7 +22,7 @@ export type Field = keyof DiscussionDoc;
|
||||
export const PROJECTION_LIST: Field[] = [
|
||||
'_id', 'domainId', 'docType', 'docId', 'highlight',
|
||||
'nReply', 'views', 'pin', 'updateAt', 'owner',
|
||||
'parentId', 'parentType', 'title',
|
||||
'parentId', 'parentType', 'title', 'hidden',
|
||||
];
|
||||
export const PROJECTION_PUBLIC: Field[] = [
|
||||
...PROJECTION_LIST, 'content', 'edited', 'react', 'maintainer',
|
||||
@@ -43,7 +45,7 @@ export const coll = db.collection('discussion.history');
|
||||
export async function add(
|
||||
domainId: string, parentType: number, parentId: ObjectId | number | string,
|
||||
owner: number, title: string, content: string,
|
||||
ip: string | null = null, highlight: boolean, pin: boolean,
|
||||
ip: string | null = null, highlight: boolean, pin: boolean, hidden = false,
|
||||
): Promise<ObjectId> {
|
||||
const time = new Date();
|
||||
const payload: Partial<DiscussionDoc> = {
|
||||
@@ -61,6 +63,7 @@ export async function add(
|
||||
updateAt: time,
|
||||
views: 0,
|
||||
sort: 100,
|
||||
hidden,
|
||||
};
|
||||
await bus.parallel('discussion/before-add', payload);
|
||||
const res = await document.add(
|
||||
@@ -271,9 +274,8 @@ export function flushNodes(domainId: string) {
|
||||
|
||||
export async function getVnode(domainId: string, type: number, id: string, uid?: number) {
|
||||
if (type === document.TYPE_PROBLEM) {
|
||||
let pdoc = await problem.get(domainId, Number.isSafeInteger(+id) ? +id : id, problem.PROJECTION_LIST);
|
||||
const pdoc = await problem.get(domainId, Number.isSafeInteger(+id) ? +id : id, problem.PROJECTION_LIST);
|
||||
if (!pdoc) throw new DiscussionNodeNotFoundError(id);
|
||||
if (pdoc.hidden) pdoc = problem.default;
|
||||
return { ...pdoc, type, id: pdoc.docId };
|
||||
}
|
||||
if ([document.TYPE_CONTEST, document.TYPE_TRAINING, document.TYPE_HOMEWORK].includes(type as any)) {
|
||||
@@ -285,6 +287,7 @@ export async function getVnode(domainId: string, type: number, id: string, uid?:
|
||||
const tsdoc = await model.getStatus(domainId, _id, uid);
|
||||
tdoc.attend = tsdoc?.attend || tsdoc?.enroll;
|
||||
}
|
||||
if (type === document.TYPE_CONTEST) tdoc.hidden = !(contest.isDone(tdoc));
|
||||
return { ...tdoc, type, id: _id };
|
||||
}
|
||||
return {
|
||||
@@ -292,6 +295,7 @@ export async function getVnode(domainId: string, type: number, id: string, uid?:
|
||||
...await getNode(domainId, id),
|
||||
type,
|
||||
id,
|
||||
owner: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -312,6 +316,16 @@ export async function getListVnodes(domainId: string, ddocs: any, getHidden = fa
|
||||
return res;
|
||||
}
|
||||
|
||||
export function checkVNodeVisibility(type: number, vnode: any, user: User) {
|
||||
if (type === document.TYPE_PROBLEM) {
|
||||
if (vnode.hidden && !(user.own(vnode) || user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN))) return false;
|
||||
}
|
||||
if ([document.TYPE_CONTEST, document.TYPE_TRAINING, document.TYPE_HOMEWORK].includes(type as any)) {
|
||||
if (!user.own(vnode) && vnode.assign?.length && !Set.intersection(vnode.assign, user.group).size) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
ctx.on('problem/delete', async (domainId, docId) => {
|
||||
const dids = await document.getMulti(
|
||||
@@ -328,6 +342,13 @@ export function apply(ctx: Context) {
|
||||
document.deleteMulti(domainId, document.TYPE_DISCUSSION_REPLY, { docId: { $in: drids } }),
|
||||
]);
|
||||
});
|
||||
ctx.on('problem/edit', async (result) => {
|
||||
const dids = await document.getMulti(
|
||||
result.domainId, document.TYPE_DISCUSSION,
|
||||
{ parentType: document.TYPE_PROBLEM, parentId: result.docId },
|
||||
).project({ docId: 1 }).map((ddoc) => ddoc.docId).toArray();
|
||||
return await coll.updateMany({ _id: { $in: dids } }, { $set: { hidden: result.hidden } });
|
||||
});
|
||||
}
|
||||
|
||||
global.Hydro.model.discussion = {
|
||||
@@ -367,4 +388,5 @@ global.Hydro.model.discussion = {
|
||||
getNodes,
|
||||
getVnode,
|
||||
getListVnodes,
|
||||
checkVNodeVisibility,
|
||||
};
|
||||
@@ -35,6 +35,9 @@
|
||||
@
|
||||
{{ datetimeSpan(ddoc.updateAt)|safe }}
|
||||
</li>
|
||||
{%- if ddoc.hidden -%}
|
||||
<li class="text-orange">({{ _('Hidden') }})</li>
|
||||
{%- endif -%}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user