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/services/core/group/invite.service.ts

264 lines
6.1 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import _ from 'lodash';
import type {
GroupInvite,
GroupInviteDocument,
GroupInviteModel,
} from '../../../models/group/invite';
import {
TcService,
TcContext,
TcDbService,
PureContext,
call,
NoPermissionError,
PERMISSION,
db,
} from 'tailchat-server-sdk';
interface GroupService
extends TcService,
TcDbService<GroupInviteDocument, GroupInviteModel> {}
class GroupService extends TcService {
get serviceName(): string {
return 'group.invite';
}
onInit(): void {
this.registerLocalDb(require('../../../models/group/invite').default);
this.registerAction('createGroupInvite', this.createGroupInvite, {
params: {
groupId: 'string',
inviteType: { type: 'enum', values: ['normal', 'permanent'] },
},
});
this.registerAction('editGroupInvite', this.editGroupInvite, {
params: {
code: 'string',
groupId: 'string',
expiredAt: { type: 'number', optional: true },
usageLimit: { type: 'number', optional: true },
},
});
this.registerAction('getAllGroupInviteCode', this.getAllGroupInviteCode, {
params: {
groupId: 'string',
},
});
this.registerAction('findInviteByCode', this.findInviteByCode, {
params: {
code: 'string',
},
});
this.registerAction('applyInvite', this.applyInvite, {
params: {
code: 'string',
},
});
this.registerAction('deleteInvite', this.deleteInvite, {
params: {
groupId: 'string',
inviteId: 'string',
},
});
}
/**
* 创建群组邀请
*/
async createGroupInvite(
ctx: TcContext<{
groupId: string;
inviteType: 'normal' | 'permanent';
}>
): Promise<GroupInvite> {
const { groupId, inviteType } = ctx.params;
const { userId, t } = ctx.meta;
const [hasNormalPermission, hasUnlimitedPermission] = await call(
ctx
).checkUserPermissions(groupId, userId, [
PERMISSION.core.invite,
PERMISSION.core.unlimitedInvite,
]);
if (
(inviteType === 'normal' && !hasNormalPermission) ||
(inviteType === 'permanent' && !hasUnlimitedPermission)
) {
throw new NoPermissionError(t('没有创建邀请码权限'));
}
const invite = await this.adapter.model.createGroupInvite(
groupId,
userId,
inviteType
);
return await this.transformDocuments(ctx, {}, invite);
}
/**
* 编辑群组邀请码
*/
async editGroupInvite(
ctx: TcContext<{
code: string;
groupId: string;
expiredAt?: number; // 时间戳单位ms
usageLimit?: number;
}>
) {
const { code, groupId, expiredAt, usageLimit } = ctx.params;
const { userId, t } = ctx.meta;
// 检查权限
const [hasEditPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.editInvite]
);
if (!hasEditPermission) {
throw new NoPermissionError(t('没有编辑邀请码权限'));
}
const update = {};
if (expiredAt) {
_.set(update, ['expiredAt'], new Date(expiredAt));
} else {
_.set(update, ['$unset', 'expiredAt'], 1);
}
if (usageLimit) {
_.set(update, ['usageLimit'], usageLimit);
} else {
_.set(update, ['$unset', 'usageLimit'], 1);
}
await this.adapter.model.updateOne({ groupId, code }, update);
return true;
}
/**
* 获取所有群组邀请码
*/
async getAllGroupInviteCode(
ctx: TcContext<{
groupId: string;
}>
) {
const groupId = ctx.params.groupId;
const { t, userId } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.manageInvite]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有查看权限'));
}
const list = await this.adapter.model.find({
groupId,
});
return await this.transformDocuments(ctx, {}, list);
}
/**
* 通过邀请码查找群组邀请信息
*/
async findInviteByCode(
ctx: PureContext<{
code: string;
}>
): Promise<GroupInvite | null> {
const code = ctx.params.code;
const invite = await this.adapter.model.findOne({
code,
});
return await this.transformDocuments(ctx, {}, invite);
}
/**
* 应用群组邀请(通过群组邀请加入群组)
*/
async applyInvite(ctx: TcContext<{ code: string }>): Promise<void> {
const code = ctx.params.code;
const t = ctx.meta.t;
const invite = await this.adapter.model.findOne({
code,
});
if (typeof invite.usageLimit === 'number') {
const usage = invite.usage || 0;
if (usage >= invite.usageLimit) {
throw new Error(t('该邀请码使用次数耗尽'));
}
}
if (new Date(invite.expiredAt).valueOf() < Date.now()) {
throw new Error(t('该邀请码已过期'));
}
const groupId = invite.groupId;
if (_.isNil(groupId)) {
throw new Error(t('群组邀请失效: 群组id为空'));
}
await ctx.call('group.joinGroup', {
groupId: String(groupId),
});
await this.adapter.model.updateOne(
{
_id: new db.Types.ObjectId(invite._id),
},
{
$inc: {
usage: 1,
},
}
);
const creatorInfo = await call(ctx).getUserInfo(String(invite.creator));
await call(ctx).addGroupSystemMessage(
String(groupId),
t('{{nickname}} 通过 {{creator}} 的邀请码加入群组', {
nickname: ctx.meta.user.nickname,
creator: creatorInfo.nickname,
})
);
}
/**
* 删除邀请码
*/
async deleteInvite(ctx: TcContext<{ groupId: string; inviteId: string }>) {
const groupId = ctx.params.groupId;
const inviteId = ctx.params.inviteId;
const { t, userId } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.manageInvite]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有删除权限'));
}
await this.adapter.model.deleteOne({
_id: inviteId,
groupId,
});
}
}
export default GroupService;