mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(server): role changed email (#9227)
This commit is contained in:
@@ -322,10 +322,6 @@ export class PermissionService {
|
||||
this.prisma.workspaceUserPermission.update({
|
||||
where: {
|
||||
workspaceId_userId: { workspaceId: ws, userId: user },
|
||||
// only update permission:
|
||||
// 1. if the new permission is owner and original permission is admin
|
||||
// 2. if the original permission is not owner
|
||||
type: toBeOwner ? Permission.Admin : { not: Permission.Owner },
|
||||
},
|
||||
data: { type: permission },
|
||||
}),
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getStreamAsBuffer } from 'get-stream';
|
||||
|
||||
import { Cache, MailService } from '../../../base';
|
||||
import { Cache, MailService, UserNotFound } from '../../../base';
|
||||
import { DocContentService } from '../../doc-renderer';
|
||||
import { PermissionService } from '../../permission';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
import { WorkspaceBlobStorage } from '../../storage';
|
||||
import { UserService } from '../../user';
|
||||
|
||||
@@ -17,6 +17,13 @@ export type InviteInfo = {
|
||||
inviteeUserId?: string;
|
||||
};
|
||||
|
||||
const PermissionToRole = {
|
||||
[Permission.Read]: 'readonly' as const,
|
||||
[Permission.Write]: 'member' as const,
|
||||
[Permission.Admin]: 'admin' as const,
|
||||
[Permission.Owner]: 'owner' as const,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
private readonly logger = new Logger(WorkspaceService.name);
|
||||
@@ -78,6 +85,27 @@ export class WorkspaceService {
|
||||
};
|
||||
}
|
||||
|
||||
private async getInviteeEmailTarget(inviteId: string) {
|
||||
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
||||
if (!inviteeUserId) {
|
||||
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
||||
return;
|
||||
}
|
||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||
const invitee = await this.user.findUserById(inviteeUserId);
|
||||
if (!invitee) {
|
||||
this.logger.error(
|
||||
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
email: invitee.email,
|
||||
workspace,
|
||||
};
|
||||
}
|
||||
|
||||
async sendAcceptedEmail(inviteId: string) {
|
||||
const { workspaceId, inviterUserId, inviteeUserId } =
|
||||
await this.getInviteInfo(inviteId);
|
||||
@@ -167,24 +195,21 @@ export class WorkspaceService {
|
||||
await this.mailer.sendReviewDeclinedEmail(email, { name: workspaceName });
|
||||
}
|
||||
|
||||
private async getInviteeEmailTarget(inviteId: string) {
|
||||
const { workspaceId, inviteeUserId } = await this.getInviteInfo(inviteId);
|
||||
if (!inviteeUserId) {
|
||||
this.logger.error(`Invitee user not found for inviteId: ${inviteId}`);
|
||||
return;
|
||||
}
|
||||
const workspace = await this.getWorkspaceInfo(workspaceId);
|
||||
const invitee = await this.user.findUserById(inviteeUserId);
|
||||
if (!invitee) {
|
||||
this.logger.error(
|
||||
`Invitee user not found in workspace: ${workspaceId}, userId: ${inviteeUserId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
async sendRoleChangedEmail(
|
||||
userId: string,
|
||||
ws: { id: string; role: Permission }
|
||||
) {
|
||||
const user = await this.user.findUserById(userId);
|
||||
if (!user) throw new UserNotFound();
|
||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||
await this.mailer.sendRoleChangedEmail(user?.email, {
|
||||
name: workspace.name,
|
||||
role: PermissionToRole[ws.role],
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
email: invitee.email,
|
||||
workspace,
|
||||
};
|
||||
async sendOwnerTransferred(email: string, ws: { id: string }) {
|
||||
const workspace = await this.getWorkspaceInfo(ws.id);
|
||||
await this.mailer.sendOwnerTransferred(email, { name: workspace.name });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
RequestMutex,
|
||||
TooManyRequest,
|
||||
URLHelper,
|
||||
UserFriendlyError,
|
||||
} from '../../../base';
|
||||
import { CurrentUser } from '../../auth';
|
||||
import { Permission, PermissionService } from '../../permission';
|
||||
@@ -311,7 +312,17 @@ export class TeamWorkspaceResolver {
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// TODO(@darkskygit): send team role changed mail
|
||||
this.event.emit('workspace.members.roleChanged', {
|
||||
userId,
|
||||
workspaceId,
|
||||
permission,
|
||||
});
|
||||
if (permission === Permission.Owner) {
|
||||
this.event.emit('workspace.members.ownerTransferred', {
|
||||
email: user.email,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -320,6 +331,10 @@ export class TeamWorkspaceResolver {
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('failed to invite user', e);
|
||||
// pass through user friendly error
|
||||
if (e instanceof UserFriendlyError) {
|
||||
return e;
|
||||
}
|
||||
return new TooManyRequest();
|
||||
}
|
||||
}
|
||||
@@ -353,4 +368,28 @@ export class TeamWorkspaceResolver {
|
||||
// send approve mail
|
||||
await this.workspaceService.sendReviewApproveEmail(inviteId);
|
||||
}
|
||||
|
||||
@OnEvent('workspace.members.roleChanged')
|
||||
async onRoleChanged({
|
||||
userId,
|
||||
workspaceId,
|
||||
permission,
|
||||
}: EventPayload<'workspace.members.roleChanged'>) {
|
||||
// send role changed mail
|
||||
await this.workspaceService.sendRoleChangedEmail(userId, {
|
||||
id: workspaceId,
|
||||
role: permission,
|
||||
});
|
||||
}
|
||||
|
||||
@OnEvent('workspace.members.ownerTransferred')
|
||||
async onOwnerTransferred({
|
||||
email,
|
||||
workspaceId,
|
||||
}: EventPayload<'workspace.members.ownerTransferred'>) {
|
||||
// send role changed mail
|
||||
await this.workspaceService.sendOwnerTransferred(email, {
|
||||
id: workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import type { FileUpload } from '../../../base';
|
||||
import {
|
||||
AlreadyInSpace,
|
||||
Cache,
|
||||
CantChangeSpaceOwner,
|
||||
DocNotFound,
|
||||
EventEmitter,
|
||||
InternalServerError,
|
||||
@@ -383,8 +382,13 @@ export class WorkspaceResolver {
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('email') email: string,
|
||||
@Args('permission', { type: () => Permission }) permission: Permission,
|
||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean
|
||||
@Args('sendInviteMail', { nullable: true }) sendInviteMail: boolean,
|
||||
@Args('permission', {
|
||||
type: () => Permission,
|
||||
nullable: true,
|
||||
deprecationReason: 'never used',
|
||||
})
|
||||
_permission?: Permission
|
||||
) {
|
||||
await this.permissions.checkWorkspace(
|
||||
workspaceId,
|
||||
@@ -392,10 +396,6 @@ export class WorkspaceResolver {
|
||||
Permission.Admin
|
||||
);
|
||||
|
||||
if (permission === Permission.Owner) {
|
||||
throw new CantChangeSpaceOwner();
|
||||
}
|
||||
|
||||
try {
|
||||
// lock to prevent concurrent invite and grant
|
||||
const lockFlag = `invite:${workspaceId}`;
|
||||
@@ -428,7 +428,7 @@ export class WorkspaceResolver {
|
||||
const inviteId = await this.permissions.grant(
|
||||
workspaceId,
|
||||
target.id,
|
||||
permission
|
||||
Permission.Write
|
||||
);
|
||||
if (sendInviteMail) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user