feat(server): send review request when team member count over quota limit (#11126)
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
import {
|
||||||
|
acceptInviteByInviteIdMutation,
|
||||||
|
createInviteLinkMutation,
|
||||||
|
WorkspaceInviteLinkExpireTime,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
|
||||||
|
import { Mockers } from '../../mocks';
|
||||||
|
import { app, e2e } from '../test';
|
||||||
|
|
||||||
|
async function createTeamWorkspace(quantity = 10) {
|
||||||
|
const owner = await app.create(Mockers.User);
|
||||||
|
|
||||||
|
const workspace = await app.create(Mockers.Workspace, {
|
||||||
|
owner: { id: owner.id },
|
||||||
|
});
|
||||||
|
await app.create(Mockers.TeamWorkspace, {
|
||||||
|
id: workspace.id,
|
||||||
|
quantity,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
owner,
|
||||||
|
workspace,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
e2e(
|
||||||
|
'should invite by link and send review request notification below quota limit',
|
||||||
|
async t => {
|
||||||
|
const { owner, workspace } = await createTeamWorkspace();
|
||||||
|
|
||||||
|
await app.login(owner);
|
||||||
|
const { createInviteLink } = await app.gql({
|
||||||
|
query: createInviteLinkMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.truthy(createInviteLink, 'failed to create invite link');
|
||||||
|
const link = createInviteLink.link;
|
||||||
|
const inviteId = link.split('/').pop()!;
|
||||||
|
|
||||||
|
// accept invite by link
|
||||||
|
await app.signup();
|
||||||
|
const result = await app.gql({
|
||||||
|
query: acceptInviteByInviteIdMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.truthy(result, 'failed to accept invite');
|
||||||
|
const notification = app.queue.last(
|
||||||
|
'notification.sendInvitationReviewRequest'
|
||||||
|
);
|
||||||
|
t.is(notification.payload.reviewerId, owner.id);
|
||||||
|
t.truthy(notification.payload.inviteId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
e2e(
|
||||||
|
'should invite by link and send review request notification over quota limit',
|
||||||
|
async t => {
|
||||||
|
const { owner, workspace } = await createTeamWorkspace(1);
|
||||||
|
|
||||||
|
await app.login(owner);
|
||||||
|
const { createInviteLink } = await app.gql({
|
||||||
|
query: createInviteLinkMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
expireTime: WorkspaceInviteLinkExpireTime.OneDay,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.truthy(createInviteLink, 'failed to create invite link');
|
||||||
|
const link = createInviteLink.link;
|
||||||
|
const inviteId = link.split('/').pop()!;
|
||||||
|
|
||||||
|
// accept invite by link
|
||||||
|
await app.signup();
|
||||||
|
const result = await app.gql({
|
||||||
|
query: acceptInviteByInviteIdMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
t.truthy(result, 'failed to accept invite');
|
||||||
|
const notification = app.queue.last(
|
||||||
|
'notification.sendInvitationReviewRequest'
|
||||||
|
);
|
||||||
|
t.is(notification.payload.reviewerId, owner.id);
|
||||||
|
t.truthy(notification.payload.inviteId);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
import { Feature } from '../../models';
|
import { Feature, FeatureType } from '../../models';
|
||||||
import { Mocker } from './factory';
|
import { Mocker } from './factory';
|
||||||
|
|
||||||
interface MockTeamWorkspaceInput {
|
interface MockTeamWorkspaceInput {
|
||||||
@@ -46,6 +46,8 @@ export class MockTeamWorkspace extends Mocker<
|
|||||||
featureId: feature.id,
|
featureId: feature.id,
|
||||||
reason: 'test',
|
reason: 'test',
|
||||||
activated: true,
|
activated: true,
|
||||||
|
name: Feature.TeamPlan,
|
||||||
|
type: FeatureType.Quota,
|
||||||
configs: {
|
configs: {
|
||||||
memberLimit: quantity,
|
memberLimit: quantity,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -610,38 +610,8 @@ export class WorkspaceResolver {
|
|||||||
`workspace:inviteLink:${workspaceId}`
|
`workspace:inviteLink:${workspaceId}`
|
||||||
);
|
);
|
||||||
if (invite?.inviteId === inviteId) {
|
if (invite?.inviteId === inviteId) {
|
||||||
const seatAvailable = await this.quota.tryCheckSeat(workspaceId);
|
await this.acceptInviteByLink(user, workspaceId);
|
||||||
if (seatAvailable) {
|
|
||||||
const invite = await this.models.workspaceUser.set(
|
|
||||||
workspaceId,
|
|
||||||
user.id,
|
|
||||||
WorkspaceRole.Collaborator,
|
|
||||||
WorkspaceMemberStatus.UnderReview
|
|
||||||
);
|
|
||||||
await this.workspaceService.sendReviewRequestNotification(invite.id);
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
const isTeam =
|
|
||||||
await this.workspaceService.isTeamWorkspace(workspaceId);
|
|
||||||
// only team workspace allow over limit
|
|
||||||
if (isTeam) {
|
|
||||||
await this.models.workspaceUser.set(
|
|
||||||
workspaceId,
|
|
||||||
user.id,
|
|
||||||
WorkspaceRole.Collaborator,
|
|
||||||
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
|
||||||
);
|
|
||||||
const memberCount =
|
|
||||||
await this.models.workspaceUser.count(workspaceId);
|
|
||||||
this.event.emit('workspace.members.updated', {
|
|
||||||
workspaceId,
|
|
||||||
count: memberCount,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw new MemberQuotaExceeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +620,40 @@ export class WorkspaceResolver {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async acceptInviteByLink(user: CurrentUser, workspaceId: string) {
|
||||||
|
const seatAvailable = await this.quota.tryCheckSeat(workspaceId);
|
||||||
|
if (seatAvailable) {
|
||||||
|
const role = await this.models.workspaceUser.set(
|
||||||
|
workspaceId,
|
||||||
|
user.id,
|
||||||
|
WorkspaceRole.Collaborator,
|
||||||
|
WorkspaceMemberStatus.UnderReview
|
||||||
|
);
|
||||||
|
await this.workspaceService.sendReviewRequestNotification(role.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTeam = await this.workspaceService.isTeamWorkspace(workspaceId);
|
||||||
|
// only team workspace allow over limit
|
||||||
|
if (isTeam) {
|
||||||
|
const role = await this.models.workspaceUser.set(
|
||||||
|
workspaceId,
|
||||||
|
user.id,
|
||||||
|
WorkspaceRole.Collaborator,
|
||||||
|
WorkspaceMemberStatus.NeedMoreSeatAndReview
|
||||||
|
);
|
||||||
|
await this.workspaceService.sendReviewRequestNotification(role.id);
|
||||||
|
const memberCount = await this.models.workspaceUser.count(workspaceId);
|
||||||
|
this.event.emit('workspace.members.updated', {
|
||||||
|
workspaceId,
|
||||||
|
count: memberCount,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MemberQuotaExceeded();
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async leaveWorkspace(
|
async leaveWorkspace(
|
||||||
@CurrentUser() user: CurrentUser,
|
@CurrentUser() user: CurrentUser,
|
||||||
|
|||||||
Reference in New Issue
Block a user