Files
speckle-server/packages/server/modules/workspaces/services/workspaceSeat.ts
T
2025-05-15 12:04:56 +03:00

157 lines
4.1 KiB
TypeScript

import { WorkspaceSeatType } from '@/modules/gatekeeper/domain/billing'
import {
CreateWorkspaceSeat,
GetWorkspaceUserSeat
} from '@/modules/gatekeeper/domain/operations'
import { NotFoundError } from '@/modules/shared/errors'
import { EventBusEmit } from '@/modules/shared/services/eventBus'
import {
AssignWorkspaceSeat,
EnsureValidWorkspaceRoleSeat,
GetWorkspaceRoleForUser
} from '@/modules/workspaces/domain/operations'
import { InvalidWorkspaceSeatTypeError } from '@/modules/workspaces/errors/workspaceSeat'
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
import { Roles, WorkspaceRoles } from '@speckle/shared'
import { z } from 'zod'
const getDefaultWorkspaceSeatTypeByWorkspaceRole = ({
workspaceRole
}: {
workspaceRole: WorkspaceRoles
}): WorkspaceSeatType => {
if (workspaceRole === Roles.Workspace.Admin) {
return WorkspaceSeatType.Editor
}
return WorkspaceSeatType.Viewer
}
const WorkspaceRoleWorkspaceSeatTypeMapping = z.union([
z.object({
workspaceRole: z.literal(Roles.Workspace.Admin),
workspaceSeatType: z.literal(WorkspaceSeatType.Editor)
}),
z.object({
workspaceRole: z.literal(Roles.Workspace.Member),
workspaceSeatType: z.union([
z.literal(WorkspaceSeatType.Editor),
z.literal(WorkspaceSeatType.Viewer)
])
}),
z.object({
workspaceRole: z.literal(Roles.Workspace.Guest),
workspaceSeatType: z.union([
z.literal(WorkspaceSeatType.Editor),
z.literal(WorkspaceSeatType.Viewer)
])
})
])
type WorkspaceRoleWorkspaceSeatTypeMapping = z.infer<
typeof WorkspaceRoleWorkspaceSeatTypeMapping
>
export const isWorkspaceRoleWorkspaceSeatTypeValid = ({
workspaceRole,
workspaceSeatType
}: {
workspaceRole: WorkspaceRoles
workspaceSeatType: WorkspaceSeatType
}): boolean => {
return WorkspaceRoleWorkspaceSeatTypeMapping.safeParse({
workspaceRole,
workspaceSeatType
}).success
}
export const ensureValidWorkspaceRoleSeatFactory =
(deps: {
createWorkspaceSeat: CreateWorkspaceSeat
getWorkspaceUserSeat: GetWorkspaceUserSeat
eventEmit: EventBusEmit
}): EnsureValidWorkspaceRoleSeat =>
async (params) => {
const workspaceSeat = await deps.getWorkspaceUserSeat({
workspaceId: params.workspaceId,
userId: params.userId
})
if (
workspaceSeat &&
isWorkspaceRoleWorkspaceSeatTypeValid({
workspaceRole: params.role,
workspaceSeatType: workspaceSeat.type
})
) {
return workspaceSeat
}
// Upsert default seat type assignment
const seat = await deps.createWorkspaceSeat({
workspaceId: params.workspaceId,
userId: params.userId,
type: getDefaultWorkspaceSeatTypeByWorkspaceRole({ workspaceRole: params.role })
})
if (!params.skipEvent) {
await deps.eventEmit({
eventName: WorkspaceEvents.SeatUpdated,
payload: {
seat,
updatedByUserId: params.updatedByUserId
}
})
}
return seat
}
export const assignWorkspaceSeatFactory =
({
createWorkspaceSeat,
getWorkspaceRoleForUser,
eventEmit: eventEmit
}: {
createWorkspaceSeat: CreateWorkspaceSeat
getWorkspaceRoleForUser: GetWorkspaceRoleForUser
eventEmit: EventBusEmit
}): AssignWorkspaceSeat =>
async ({ workspaceId, userId, type, assignedByUserId }) => {
const workspaceAcl = await getWorkspaceRoleForUser({ workspaceId, userId })
if (!workspaceAcl) {
throw new NotFoundError('User does not have a role in the workspace')
}
if (
!isWorkspaceRoleWorkspaceSeatTypeValid({
workspaceRole: workspaceAcl.role,
workspaceSeatType: type
})
) {
throw new InvalidWorkspaceSeatTypeError(
`User with workspace role ${workspaceAcl.role} cannot have a seat of type ${type}`,
{
info: {
workspaceId,
userId
}
}
)
}
const seat = await createWorkspaceSeat({
workspaceId,
userId,
type
})
await eventEmit({
eventName: WorkspaceEvents.SeatUpdated,
payload: {
seat,
updatedByUserId: assignedByUserId
}
})
return seat
}