bugfix: a new flow added to disable staffs and free the used seats.
This commit is contained in:
@@ -756,7 +756,18 @@ export class AuthService {
|
||||
throw new UnauthorizedException('Access denied to this organization');
|
||||
}
|
||||
if (!membership.isOwner && !membership.isActive) {
|
||||
throw new UnauthorizedException('Your invitation is still pending activation');
|
||||
const acceptedInvite = await this.prisma.staffInvitation.findFirst({
|
||||
where: {
|
||||
membershipId: membership.id,
|
||||
acceptedAt: { not: null },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
throw new UnauthorizedException(
|
||||
acceptedInvite
|
||||
? 'Your access to this organization has been disabled.'
|
||||
: 'Your invitation is still pending activation.',
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Build payload WITH org context
|
||||
|
||||
@@ -80,6 +80,19 @@ export class StaffController {
|
||||
return this.staffService.updateMember(req.user.id, organizationId, membershipId, dto);
|
||||
}
|
||||
|
||||
@Patch('members/:membershipId/disable')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({
|
||||
summary: 'Disable staff member (frees a seat; member cannot access this organization)',
|
||||
})
|
||||
disableMember(
|
||||
@Req() req: { user: { id: string; organizationId?: string } },
|
||||
@Param('membershipId') membershipId: string,
|
||||
) {
|
||||
const organizationId = this.staffService.getOrganizationIdFromUser(req.user);
|
||||
return this.staffService.disableMember(req.user.id, organizationId, membershipId);
|
||||
}
|
||||
|
||||
@Delete('members/:membershipId')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({ summary: 'Remove staff member from organization' })
|
||||
|
||||
@@ -399,6 +399,44 @@ export class StaffService {
|
||||
return { success: true, message: 'Member updated' };
|
||||
}
|
||||
|
||||
async disableMember(actorUserId: string, organizationId: string, membershipId: string) {
|
||||
const actor = await this.getActorMembership(actorUserId, organizationId);
|
||||
if (!actor || !this.canEditStaff(actor)) {
|
||||
throw new ForbiddenException('You cannot manage staff');
|
||||
}
|
||||
|
||||
const target = await this.prisma.membership.findFirst({
|
||||
where: { id: membershipId, organizationId },
|
||||
});
|
||||
|
||||
if (!target) {
|
||||
throw new NotFoundException('Member not found');
|
||||
}
|
||||
if (target.isOwner) {
|
||||
throw new ForbiddenException('Cannot disable the organization owner');
|
||||
}
|
||||
if (actorUserId === target.userId) {
|
||||
throw new BadRequestException('You cannot disable your own access');
|
||||
}
|
||||
if (!target.isActive) {
|
||||
throw new BadRequestException('This member is already disabled or pending activation');
|
||||
}
|
||||
|
||||
await this.prisma.membership.update({
|
||||
where: { id: membershipId },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
await this.prisma.session.deleteMany({
|
||||
where: { userId: target.userId },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Member disabled. Their seat is now available for another invite.',
|
||||
};
|
||||
}
|
||||
|
||||
async removeMember(actorUserId: string, organizationId: string, membershipId: string) {
|
||||
const actor = await this.getActorMembership(actorUserId, organizationId);
|
||||
if (!actor || !this.canEditStaff(actor)) {
|
||||
@@ -435,11 +473,12 @@ export class StaffService {
|
||||
isOwner: boolean;
|
||||
isActive: boolean;
|
||||
invitations: { acceptedAt: Date | null; revokedAt: Date | null; expiresAt: Date }[];
|
||||
}): 'ACTIVE' | 'PENDING' | 'EXPIRED' {
|
||||
if (m.isOwner || m.isActive) return 'ACTIVE';
|
||||
}): 'ACTIVE' | 'PENDING' | 'EXPIRED' | 'DISABLED' {
|
||||
if (m.isOwner) return 'ACTIVE';
|
||||
if (m.isActive) return 'ACTIVE';
|
||||
const invitation = m.invitations[0];
|
||||
if (!invitation) return 'EXPIRED';
|
||||
if (invitation.acceptedAt) return 'ACTIVE';
|
||||
if (invitation?.acceptedAt) return 'DISABLED';
|
||||
if (!invitation) return 'DISABLED';
|
||||
if (invitation.revokedAt) return 'EXPIRED';
|
||||
return invitation.expiresAt.getTime() > Date.now() ? 'PENDING' : 'EXPIRED';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user