You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/server/models/group/group.ts

366 lines
7.8 KiB
TypeScript

import {
getModelForClass,
prop,
DocumentType,
Ref,
ReturnModelType,
modelOptions,
Severity,
} from '@typegoose/typegoose';
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses';
import _ from 'lodash';
import { Types } from 'mongoose';
import {
allPermission,
call,
GroupPanelType,
NoPermissionError,
PERMISSION,
TcContext,
} from 'tailchat-server-sdk';
import { User } from '../user/user';
class GroupMember {
@prop({
type: () => String,
})
roles?: string[]; // 角色权限组id
@prop({
ref: () => User,
})
userId: Ref<User>;
/**
* xxx
*/
@prop()
muteUntil?: Date;
}
@modelOptions({
options: {
allowMixed: Severity.ALLOW,
},
})
export class GroupPanel {
@prop()
id: string; // 在群组中唯一, 可以用任意方式进行生成。这里使用ObjectId, 但不是ObjectId类型
@prop()
name: string; // 用于显示的名称
@prop()
parentId?: string; // 父节点id
/**
* :
* 0
* 1
* 2
*
* Reference: https://discord.com/developers/docs/resources/channel#channel-object-channel-types
*/
@prop({
type: () => Number,
})
type: GroupPanelType;
@prop()
provider?: string; // 面板提供者,为插件的标识,仅面板类型为插件时有效
@prop()
pluginPanelName?: string; // 插件面板名, 如 com.msgbyte.webview/grouppanel
/**
*
*/
@prop()
meta?: object;
}
/**
*
*/
export class GroupRole implements Base {
_id: Types.ObjectId;
id: string;
@prop()
name: string; // 权限组名
@prop({
type: () => String,
})
permissions: string[]; // 拥有的权限, 是一段字符串
}
/**
*
*/
export class Group extends TimeStamps implements Base {
_id: Types.ObjectId;
id: string;
@prop({
trim: true,
maxlength: [100, 'group name is too long'],
})
name!: string;
@prop()
avatar?: string;
@prop({
ref: () => User,
})
owner: Ref<User>;
@prop({ type: () => GroupMember, _id: false })
members: GroupMember[];
@prop({ type: () => GroupPanel, _id: false })
panels: GroupPanel[];
@prop({
type: () => GroupRole,
default: [],
})
roles?: GroupRole[];
/**
*
*
*/
@prop({
type: () => String,
default: () => [],
})
fallbackPermissions: string[];
/**
*
*/
static async createGroup(
this: ReturnModelType<typeof Group>,
options: {
name: string;
avatarBase64?: string; // base64版本的头像字符串
panels?: GroupPanel[];
owner: string;
}
): Promise<GroupDocument> {
const { name, avatarBase64, panels = [], owner } = options;
if (typeof avatarBase64 === 'string') {
// TODO: 处理头像上传逻辑
}
// 预处理panels信息, 变换ID为objectid
const panelSectionMap: Record<string, string> = {};
panels.forEach((panel) => {
const originPanelId = panel.id;
panel.id = String(new Types.ObjectId());
if (panel.type === GroupPanelType.GROUP) {
panelSectionMap[originPanelId] = panel.id;
}
if (typeof panel.parentId === 'string') {
if (typeof panelSectionMap[panel.parentId] !== 'string') {
throw new Error('创建失败, 面板参数不合法');
}
panel.parentId = panelSectionMap[panel.parentId];
}
});
// NOTE: Expression produces a union type that is too complex to represent.
const res = await this.create({
name,
panels,
owner,
members: [
{
roles: [],
userId: owner,
},
],
});
return res;
}
/**
*
* @param userId ID
*/
static async getUserGroups(
this: ReturnModelType<typeof Group>,
userId: string
): Promise<GroupDocument[]> {
return this.find({
'members.userId': userId,
});
}
/**
*
*/
static async updateGroupRoleName(
this: ReturnModelType<typeof Group>,
groupId: string,
roleId: string,
roleName: string
): Promise<Group> {
const group = await this.findById(groupId);
if (!group) {
throw new Error('Not Found Group');
}
const modifyRole = group.roles.find((role) => String(role._id) === roleId);
if (!modifyRole) {
throw new Error('Not Found Role');
}
modifyRole.name = roleName;
await group.save();
return group;
}
/**
*
*/
static async updateGroupRolePermission(
this: ReturnModelType<typeof Group>,
groupId: string,
roleId: string,
permissions: string[]
): Promise<Group> {
const group = await this.findById(groupId);
if (!group) {
throw new Error('Not Found Group');
}
const modifyRole = group.roles.find((role) => String(role._id) === roleId);
if (!modifyRole) {
throw new Error('Not Found Role');
}
modifyRole.permissions = [...permissions];
await group.save();
return group;
}
/**
*
*/
static async getGroupUserPermission(
this: ReturnModelType<typeof Group>,
groupId: string,
userId: string
): Promise<string[]> {
const group = await this.findById(groupId);
if (!group) {
throw new Error('Not Found Group');
}
const member = group.members.find(
(member) => String(member.userId) === userId
);
if (!member) {
throw new Error('Not Found Member');
}
const allRoles = member.roles;
const allRolesPermission = allRoles.map((roleName) => {
const p = group.roles.find((r) => r.name === roleName);
return p?.permissions ?? [];
});
if (String(group.owner) === userId) {
/**
*
*
*/
return _.uniq([
...allPermission,
..._.flatten(allRolesPermission),
...group.fallbackPermissions,
]);
} else {
return _.uniq([
..._.flatten(allRolesPermission),
...group.fallbackPermissions,
]);
}
}
/**
*
*
*
*/
static async updateGroupMemberField<
K extends keyof Pick<GroupMember, 'roles' | 'muteUntil'>
>(
this: ReturnModelType<typeof Group>,
ctx: TcContext,
groupId: string,
memberId: string,
fieldName: K,
fieldValue: GroupMember[K] | ((member: GroupMember) => void),
operatorUserId: string
): Promise<Group> {
const group = await this.findById(groupId);
const t = ctx.meta.t;
if (fieldName === 'roles') {
// 检查操作用户是否有管理角色的权限
const [hasRolePermission] = await call(ctx).checkUserPermissions(
groupId,
operatorUserId,
[PERMISSION.core.manageRoles]
);
if (!hasRolePermission) {
throw new NoPermissionError(t('没有操作角色权限'));
}
} else {
// 检查操作用户是否有管理用户权限
const [hasUserPermission] = await call(ctx).checkUserPermissions(
groupId,
operatorUserId,
[PERMISSION.core.manageUser]
);
if (!hasUserPermission) {
throw new NoPermissionError(t('没有操作用户权限'));
}
}
const member = group.members.find((m) => String(m.userId) === memberId);
if (!member) {
throw new Error(t('没有找到该成员'));
}
if (typeof fieldValue === 'function') {
fieldValue(member);
} else {
member[fieldName] = fieldValue;
}
await group.save();
return group;
}
}
export type GroupDocument = DocumentType<Group>;
const model = getModelForClass(Group);
export type GroupModel = typeof model;
export default model;