core: allow lock topic

This commit is contained in:
undefined committed 2022-02-12 19:21:35 +08:00
1 parent 931e96a6b4
commit c305e57860
11 files changed
+49 -18

No files matched your search

+3
View File
@@ -437,6 +437,7 @@ List View: 列表视图
Live: 正在进行
Live...: 正在进行…
Location: 位置
Lock Topic: 锁定主题
Login to Attend Contest: 登录后参加比赛
Login to Claim Homework: 登录后认领作业
Login to Create a Discussion: 登录后创建讨论
@@ -750,6 +751,7 @@ Terms of Service: 服务条款
Test data comes from: 测试数据来自
Testdata Config: 测试数据配置
Text: 文本
The discussion is locked, you can not reply anymore.: 此讨论已经锁定,您不能再回复。
The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。
The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。
The invitation code you provided is invalid.: 您提供的邀请码无效。
@@ -800,6 +802,7 @@ Unhide Selected: 取消隐藏选中
Unique: 唯一
Unknown field {0}.: 未知字段 {0}。
Unknown: 未知
Unlock Topic: 解锁主题
Unordered List: 无序列表
Unsaved session expire seconds: 未保存的会话的过期秒数
Unstar Topic: 取消收藏
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "3.4.24",
"version": "3.4.25",
"bin": "bin/hydrooj.js",
"main": "src/loader",
"module": "src/loader",
+1
View File
@@ -98,6 +98,7 @@ export const DomainJoinForbiddenError = Err('DomainJoinForbiddenError', Forbidde
export const DomainJoinAlreadyMemberError = Err('DomainJoinAlreadyMemberError', ForbiddenError, 'Failed to join the domain. You are already a member.');
export const InvalidJoinInvitationCodeError = Err('InvalidJoinInvitationCodeError', ForbiddenError, 'The invitation code you provided is invalid.');
export const CurrentPasswordError = Err('CurrentPasswordError', ForbiddenError, "Current password doesn't match.");
export const DiscussionLockedError = Err('DiscussionLockedError', ForbiddenError, 'The discussion is locked, you can not reply anymore.');
export const UserNotFoundError = Err('UserNotFoundError', NotFoundError, 'User {0} not found.');
export const NoProblemError = Err('NoProblemError', NotFoundError, 'No problem.');
+17 -2
View File
@@ -1,6 +1,9 @@
import { isSafeInteger } from 'lodash';
import { ObjectID } from 'mongodb';
import { DiscussionNotFoundError, DocumentNotFoundError, PermissionError } from '../error';
import {
DiscussionLockedError, DiscussionNotFoundError, DocumentNotFoundError,
PermissionError,
} from '../error';
import { DiscussionDoc, DiscussionReplyDoc, DiscussionTailReplyDoc } from '../interface';
import paginate from '../lib/paginate';
import { PERM, PRIV } from '../model/builtin';
@@ -207,6 +210,14 @@ class DiscussionDetailHandler extends DiscussionHandler {
this.checkPriv(PRIV.PRIV_USER_PROFILE);
}
@param('did', Types.ObjectID)
@param('lock', Types.Boolean)
async postSetLock(domainId: string, did: ObjectID, lock: boolean) {
if (!this.user.own(this.ddoc)) this.checkPerm(PERM.PERM_LOCK_DISCUSSION);
await discussion.edit(domainId, did, { lock });
this.back();
}
@param('type', Types.Range(['did', 'drid']))
@param('id', Types.ObjectID)
@param('emoji', Types.Emoji)
@@ -223,6 +234,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
@param('content', Types.Content)
async postReply(domainId: string, did: ObjectID, content: string) {
this.checkPerm(PERM.PERM_REPLY_DISCUSSION);
if (this.ddoc.lock) throw new DiscussionLockedError(domainId, did);
await this.limitRate('add_discussion', 3600, 60);
const targets = new Set(Array.from(content.matchAll(/@\[\]\(\/user\/(\d+)\)/g)).map((i) => +i[1]));
const uids = Object.keys(await user.getList(domainId, Array.from(targets))).map((i) => +i);
@@ -241,6 +253,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
@param('content', Types.Content)
async postTailReply(domainId: string, drid: ObjectID, content: string) {
this.checkPerm(PERM.PERM_REPLY_DISCUSSION);
if (this.ddoc.lock) throw new DiscussionLockedError(domainId, this.ddoc.docId);
await this.limitRate('add_discussion', 3600, 60);
const targets = new Set(Array.from(content.matchAll(/@\[\]\(\/user\/(\d+)\)/g)).map((i) => +i[1]));
const uids = Object.keys(await user.getList(domainId, Array.from(targets))).map((i) => +i);
@@ -355,7 +368,9 @@ class DiscussionEditHandler extends DiscussionHandler {
if (!this.user.hasPerm(PERM.PERM_HIGHLIGHT_DISCUSSION)) highlight = this.ddoc.highlight;
if (!this.user.hasPerm(PERM.PERM_PIN_DISCUSSION)) pin = this.ddoc.pin;
await Promise.all([
discussion.edit(domainId, did, title, content, highlight, pin),
discussion.edit(domainId, did, {
title, content, highlight, pin,
}),
oplog.log(this, 'discussion.edit', this.ddoc),
]);
this.response.body = { did };
+1
View File
@@ -435,6 +435,7 @@ declare module './model/discussion' {
react: Record<string, number>;
sort: number;
lastRCount: number;
lock?: boolean;
}
}
+2
View File
@@ -58,6 +58,7 @@ export const PERM = {
PERM_DELETE_DISCUSSION_REPLY: 1n << 38n,
PERM_DELETE_DISCUSSION_REPLY_SELF: 1n << 39n,
PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION: 1n << 40n,
PERM_LOCK_DISCUSSION: 64n,
// Contest
PERM_VIEW_CONTEST: 1n << 41n,
@@ -131,6 +132,7 @@ export const PERMS = [
Permission('perm_discussion', PERM.PERM_PIN_DISCUSSION, 'Pin discussions'),
Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION, 'Edit discussions'),
Permission('perm_discussion', PERM.PERM_EDIT_DISCUSSION_SELF, 'Edit own discussions'),
Permission('perm_discussion', PERM.PERM_LOCK_DISCUSSION, 'Lock discussions'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION, 'Delete discussions'),
Permission('perm_discussion', PERM.PERM_DELETE_DISCUSSION_SELF, 'Delete own discussions'),
Permission('perm_discussion', PERM.PERM_REPLY_DISCUSSION, 'Reply discussions'),
+3 -8
View File
@@ -57,6 +57,7 @@ export const PROJECTION_LIST: DiscussionDoc.Field[] = [
];
export const PROJECTION_PUBLIC: DiscussionDoc.Field[] = [
...PROJECTION_LIST, 'content', 'history', 'react', 'maintainer',
'lock',
];
export const typeDisplay = {
@@ -103,14 +104,8 @@ export async function get<T extends DiscussionDoc.Field>(
return await document.get(domainId, document.TYPE_DISCUSSION, did, projection);
}
export function edit(
domainId: string, did: ObjectID,
title: string, content: string, highlight: boolean, pin: boolean,
): Promise<DiscussionDoc | null> {
const payload = {
title, content, highlight, pin,
};
return document.set(domainId, document.TYPE_DISCUSSION, did, payload);
export function edit(domainId: string, did: ObjectID, $set: Partial<DiscussionDoc>) {
return document.set(domainId, document.TYPE_DISCUSSION, did, $set);
}
export function inc(
+1 -1
View File
@@ -26,7 +26,7 @@ class MessageModel {
from, to, content, _id: res.insertedId, flag,
};
if (from !== to) bus.broadcast('user/message', to, mdoc);
await user.inc(to, 'unreadMsg', 1);
if (flag & MessageModel.FLAG_UNREAD) await user.inc(to, 'unreadMsg', 1);
return mdoc;
}