feat(gatekeeper): add per workspace feature flags (#5303)
* 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
This commit is contained in:
@@ -33,7 +33,12 @@ import type { EventBusEmit, EventPayload } from '@/modules/shared/services/event
|
||||
import { getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants'
|
||||
import type { MaybeNullOrUndefined, StreamRoles, WorkspaceRoles } from '@speckle/shared'
|
||||
import { isPaidPlan, Roles, throwUncoveredError } from '@speckle/shared'
|
||||
import {
|
||||
isPaidPlan,
|
||||
Roles,
|
||||
throwUncoveredError,
|
||||
WorkspaceFeatureFlags
|
||||
} from '@speckle/shared'
|
||||
import type {
|
||||
QueryAllProjects,
|
||||
UpsertProjectRole
|
||||
@@ -965,7 +970,8 @@ export const initializeEventListenersFactory =
|
||||
status: WorkspacePlanStatuses.Valid,
|
||||
workspaceId: payload.workspace.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
updatedAt: new Date(),
|
||||
featureFlags: WorkspaceFeatureFlags.none
|
||||
}
|
||||
await upsertUnpaidWorkspacePlanFactory({ db })({ workspacePlan })
|
||||
await eventBus.emit({
|
||||
|
||||
@@ -114,6 +114,7 @@ import {
|
||||
import type { WorkspaceRoles } from '@speckle/shared'
|
||||
import {
|
||||
Roles,
|
||||
WorkspaceFeatureFlags,
|
||||
WorkspacePlanFeatures,
|
||||
WorkspacePlans,
|
||||
removeNullOrUndefinedKeys,
|
||||
@@ -229,6 +230,7 @@ import {
|
||||
import { WorkspaceInvitesLimit } from '@/modules/workspaces/domain/constants'
|
||||
import { copyWorkspaceFactory } from '@/modules/workspaces/repositories/projectRegions'
|
||||
import { queryAllProjectsFactory } from '@/modules/core/services/projects'
|
||||
import { WorkspacePlanNotFoundError } from '@/modules/gatekeeper/errors/billing'
|
||||
|
||||
const eventBus = getEventBus()
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -607,6 +609,42 @@ export default FF_WORKSPACES_MODULE_ENABLED
|
||||
operationDescription: 'Update workspace plan'
|
||||
}
|
||||
)
|
||||
return true
|
||||
},
|
||||
giveAccessToWorkspaceFeature: async (_parent, { input }, ctx) => {
|
||||
const { workspaceId, featureFlagName } = input
|
||||
const userId = ctx.userId
|
||||
if (!userId) throw new UnauthorizedError()
|
||||
|
||||
const featureFlag = WorkspaceFeatureFlags[featureFlagName]
|
||||
|
||||
const workspacePlan = await getWorkspacePlanFactory({ db })({ workspaceId })
|
||||
if (!workspacePlan) throw new WorkspacePlanNotFoundError()
|
||||
|
||||
workspacePlan.featureFlags |= featureFlag
|
||||
// not updating updatedAt here deliberately. Feature flags are internal for now
|
||||
await upsertWorkspacePlanFactory({ db })({
|
||||
workspacePlan
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
removeAccessToWorkspaceFeature: async (_parent, { input }, ctx) => {
|
||||
const { workspaceId, featureFlagName } = input
|
||||
const userId = ctx.userId
|
||||
if (!userId) throw new UnauthorizedError()
|
||||
|
||||
const featureFlag = WorkspaceFeatureFlags[featureFlagName]
|
||||
|
||||
const workspacePlan = await getWorkspacePlanFactory({ db })({ workspaceId })
|
||||
if (!workspacePlan) throw new WorkspacePlanNotFoundError()
|
||||
|
||||
workspacePlan.featureFlags ^= featureFlag
|
||||
// not updating updatedAt here deliberately. Feature flags are internal for now
|
||||
await upsertWorkspacePlanFactory({ db })({
|
||||
workspacePlan
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user