6982023dca
* feat(gatekeeper): add per workspace feature flags * feat(workspaces): add admin api for granting and removing access to workspace features * fix(workspaces): use the correct constant name * fix(workspaces): more test type fixes * fix(shared): fix tests and types * fix(workspaces): properly use exhaustive switch statement * fix(workspaces): add new workspace plan feature to switch * fix(workspaces): use regular integer, its fine for now... * fix(workspaces): feature flag retention post checkout * fix(gatekeeper): fix upsert plan tests
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import type {
|
|
GetWorkspacePlan,
|
|
GetWorkspaceSubscription,
|
|
UpsertWorkspacePlan
|
|
} from '@/modules/gatekeeper/domain/billing'
|
|
import {
|
|
InvalidWorkspacePlanStatus,
|
|
WorkspacePlanNotFoundError
|
|
} from '@/modules/gatekeeper/errors/billing'
|
|
import { GatekeeperEvents } from '@/modules/gatekeeperCore/domain/events'
|
|
import type { EventBusEmit } from '@/modules/shared/services/eventBus'
|
|
import type { GetWorkspace } from '@/modules/workspaces/domain/operations'
|
|
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
|
|
import type { WorkspacePlan } from '@speckle/shared'
|
|
import { throwUncoveredError, WorkspacePlans } from '@speckle/shared'
|
|
|
|
export const updateWorkspacePlanFactory =
|
|
({
|
|
getWorkspace,
|
|
upsertWorkspacePlan,
|
|
getWorkspacePlan,
|
|
getWorkspaceSubscription,
|
|
emitEvent
|
|
}: {
|
|
getWorkspace: GetWorkspace
|
|
// im using the generic function here, cause the service is
|
|
// responsible for protecting the permutations
|
|
upsertWorkspacePlan: UpsertWorkspacePlan
|
|
getWorkspacePlan: GetWorkspacePlan
|
|
getWorkspaceSubscription: GetWorkspaceSubscription
|
|
emitEvent: EventBusEmit
|
|
}) =>
|
|
async ({
|
|
userId,
|
|
workspaceId,
|
|
name,
|
|
status
|
|
}: Pick<WorkspacePlan, 'workspaceId' | 'name' | 'status'> & {
|
|
userId: string | null
|
|
}): Promise<void> => {
|
|
const workspace = await getWorkspace({
|
|
workspaceId
|
|
})
|
|
if (!workspace) throw new WorkspaceNotFoundError()
|
|
let workspacePlan: WorkspacePlan
|
|
const previousWorkspacePlan = await getWorkspacePlan({ workspaceId })
|
|
if (!previousWorkspacePlan) throw new WorkspacePlanNotFoundError()
|
|
const workspaceSubscription = await getWorkspaceSubscription({ workspaceId })
|
|
|
|
const createdAt = new Date()
|
|
const updatedAt = new Date()
|
|
switch (name) {
|
|
case WorkspacePlans.Team:
|
|
case WorkspacePlans.TeamUnlimited:
|
|
case WorkspacePlans.Pro:
|
|
case WorkspacePlans.ProUnlimited:
|
|
switch (status) {
|
|
case 'valid':
|
|
case 'cancelationScheduled':
|
|
case 'canceled':
|
|
case 'paymentFailed':
|
|
workspacePlan = {
|
|
workspaceId,
|
|
status,
|
|
name,
|
|
createdAt,
|
|
updatedAt,
|
|
featureFlags: previousWorkspacePlan.featureFlags
|
|
}
|
|
await upsertWorkspacePlan({ workspacePlan })
|
|
break
|
|
default:
|
|
throwUncoveredError(status)
|
|
}
|
|
break
|
|
|
|
case WorkspacePlans.Free:
|
|
case WorkspacePlans.Academia:
|
|
case WorkspacePlans.Unlimited:
|
|
case WorkspacePlans.Enterprise:
|
|
case WorkspacePlans.TeamUnlimitedInvoiced:
|
|
case WorkspacePlans.ProUnlimitedInvoiced:
|
|
switch (status) {
|
|
case 'valid':
|
|
if (workspaceSubscription) throw new InvalidWorkspacePlanStatus()
|
|
|
|
workspacePlan = {
|
|
workspaceId,
|
|
status,
|
|
name,
|
|
createdAt,
|
|
updatedAt,
|
|
featureFlags: previousWorkspacePlan.featureFlags
|
|
}
|
|
await upsertWorkspacePlan({ workspacePlan })
|
|
break
|
|
case 'cancelationScheduled':
|
|
case 'canceled':
|
|
case 'paymentFailed':
|
|
throw new InvalidWorkspacePlanStatus()
|
|
default:
|
|
throwUncoveredError(status)
|
|
}
|
|
break
|
|
default:
|
|
throwUncoveredError(name)
|
|
}
|
|
|
|
await emitEvent({
|
|
eventName: GatekeeperEvents.WorkspacePlanUpdated,
|
|
payload: {
|
|
userId,
|
|
workspacePlan,
|
|
previousWorkspacePlan
|
|
}
|
|
})
|
|
}
|