Merge branch 'main' of github.com:specklesystems/speckle-server into alessandro/web-2304-expose-graphql-field-readonly-for-workspace-type
This commit is contained in:
@@ -105,6 +105,18 @@ export type GetWorkspaceWithDomains = (args: {
|
||||
|
||||
export type DeleteWorkspace = (args: DeleteWorkspaceArgs) => Promise<void>
|
||||
|
||||
type CountWorkspacesArgs = {
|
||||
filter?: {
|
||||
search?: string
|
||||
}
|
||||
}
|
||||
export type QueryWorkspacesArgs = CountWorkspacesArgs & {
|
||||
limit: number
|
||||
cursor?: string
|
||||
}
|
||||
export type QueryWorkspaces = (args: QueryWorkspacesArgs) => Promise<Workspace[]>
|
||||
export type CountWorkspaces = (args: CountWorkspacesArgs) => Promise<number>
|
||||
|
||||
/** Workspace Roles */
|
||||
|
||||
export type GetWorkspaceCollaboratorsArgs = {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { initializeModuleEventEmitter } from '@/modules/shared/services/moduleEventEmitterSetup'
|
||||
import {
|
||||
WorkspaceEvents,
|
||||
WorkspaceEventsPayloads
|
||||
} from '@/modules/workspacesCore/domain/events'
|
||||
|
||||
const { emit, listen } = initializeModuleEventEmitter<WorkspaceEventsPayloads>({
|
||||
moduleName: 'workspaces'
|
||||
})
|
||||
|
||||
export const WorkspacesEmitter = { emit, listen, events: WorkspaceEvents }
|
||||
@@ -2,7 +2,7 @@ import { db } from '@/db/knex'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
|
||||
import { canWorkspaceUseRegionsFactory } from '@/modules/gatekeeper/services/featureAuthorization'
|
||||
import { getDb } from '@/modules/multiregion/dbSelector'
|
||||
import { getDb } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { getRegionsFactory } from '@/modules/multiregion/repositories'
|
||||
import { authorizeResolver } from '@/modules/shared'
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { db } from '@/db/knex'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import {
|
||||
Resolvers,
|
||||
WorkspacePlans,
|
||||
WorkspacePlanStatuses
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
||||
import {
|
||||
getProjectCollaboratorsFactory,
|
||||
@@ -50,8 +54,7 @@ import {
|
||||
WorkspaceJoinNotAllowedError,
|
||||
WorkspaceNotFoundError,
|
||||
WorkspacePaidPlanActiveError,
|
||||
WorkspacesNotAuthorizedError,
|
||||
WorkspacesNotYetImplementedError
|
||||
WorkspacesNotAuthorizedError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
deleteWorkspaceFactory as repoDeleteWorkspaceFactory,
|
||||
@@ -73,7 +76,9 @@ import {
|
||||
getWorkspaceBySlugFactory,
|
||||
countDomainsByWorkspaceIdFactory,
|
||||
getWorkspaceCreationStateFactory,
|
||||
upsertWorkspaceCreationStateFactory
|
||||
upsertWorkspaceCreationStateFactory,
|
||||
queryWorkspacesFactory,
|
||||
countWorkspacesFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
buildWorkspaceInviteEmailContentsFactory,
|
||||
@@ -105,7 +110,6 @@ import {
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
import {
|
||||
getDiscoverableWorkspacesForUserFactory,
|
||||
getPaginatedWorkspaceTeamFactory,
|
||||
getWorkspacesForUserFactory
|
||||
} from '@/modules/workspaces/services/retrieval'
|
||||
import {
|
||||
@@ -163,7 +167,7 @@ import {
|
||||
isRateLimitBreached
|
||||
} from '@/modules/core/services/ratelimiter'
|
||||
import { RateLimitError } from '@/modules/core/errors/ratelimit'
|
||||
import { getRegionDb } from '@/modules/multiregion/dbSelector'
|
||||
import { getRegionDb } from '@/modules/multiregion/utils/dbSelector'
|
||||
import {
|
||||
listUserExpiredSsoSessionsFactory,
|
||||
listWorkspaceSsoMembershipsByUserEmailFactory
|
||||
@@ -189,8 +193,15 @@ import {
|
||||
} from '@/modules/automate/services/authCode'
|
||||
import { getGenericRedis } from '@/modules/shared/redis/redis'
|
||||
import { convertFunctionToGraphQLReturn } from '@/modules/automate/services/functionManagement'
|
||||
import { getWorkspacePlanFactory } from '@/modules/gatekeeper/repositories/billing'
|
||||
import {
|
||||
getWorkspacePlanFactory,
|
||||
upsertPaidWorkspacePlanFactory,
|
||||
upsertTrialWorkspacePlanFactory,
|
||||
upsertUnpaidWorkspacePlanFactory
|
||||
} from '@/modules/gatekeeper/repositories/billing'
|
||||
import { Knex } from 'knex'
|
||||
import { getPaginatedItemsFactory } from '@/modules/shared/services/paginatedItems'
|
||||
import { InvalidWorkspacePlanStatus } from '@/modules/gatekeeper/errors/billing'
|
||||
|
||||
const eventBus = getEventBus()
|
||||
const getServerInfo = getServerInfoFactory({ db })
|
||||
@@ -357,7 +368,8 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
workspaceMutations: () => ({})
|
||||
workspaceMutations: () => ({}),
|
||||
admin: () => ({})
|
||||
},
|
||||
ProjectInviteMutations: {
|
||||
async createForWorkspace(_parent, args, ctx) {
|
||||
@@ -414,6 +426,73 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return ctx.loaders.streams.getStream.load(args.projectId)
|
||||
}
|
||||
},
|
||||
AdminMutations: {
|
||||
updateWorkspacePlan: async (_parent, { input }) => {
|
||||
const { workspaceId, plan: name, status } = input
|
||||
const workspace = await getWorkspaceFactory({ db })({
|
||||
workspaceId
|
||||
})
|
||||
const createdAt = new Date()
|
||||
if (!workspace) throw new WorkspaceNotFoundError()
|
||||
switch (name) {
|
||||
case WorkspacePlans.Starter:
|
||||
switch (status) {
|
||||
case WorkspacePlanStatuses.Trial:
|
||||
case WorkspacePlanStatuses.Expired:
|
||||
await upsertTrialWorkspacePlanFactory({ db })({
|
||||
workspacePlan: { workspaceId, status, name, createdAt }
|
||||
})
|
||||
return true
|
||||
case WorkspacePlanStatuses.Valid:
|
||||
case WorkspacePlanStatuses.CancelationScheduled:
|
||||
case WorkspacePlanStatuses.Canceled:
|
||||
case WorkspacePlanStatuses.PaymentFailed:
|
||||
await upsertPaidWorkspacePlanFactory({ db })({
|
||||
workspacePlan: { workspaceId, status, name, createdAt }
|
||||
})
|
||||
return true
|
||||
default:
|
||||
throwUncoveredError(status)
|
||||
}
|
||||
case WorkspacePlans.Business:
|
||||
case WorkspacePlans.Plus:
|
||||
switch (status) {
|
||||
case WorkspacePlanStatuses.Trial:
|
||||
case WorkspacePlanStatuses.Expired:
|
||||
throw new InvalidWorkspacePlanStatus()
|
||||
case WorkspacePlanStatuses.Valid:
|
||||
case WorkspacePlanStatuses.CancelationScheduled:
|
||||
case WorkspacePlanStatuses.Canceled:
|
||||
case WorkspacePlanStatuses.PaymentFailed:
|
||||
await upsertPaidWorkspacePlanFactory({ db })({
|
||||
workspacePlan: { workspaceId, status, name, createdAt }
|
||||
})
|
||||
return true
|
||||
default:
|
||||
throwUncoveredError(status)
|
||||
}
|
||||
|
||||
case WorkspacePlans.Academia:
|
||||
case WorkspacePlans.Unlimited:
|
||||
switch (status) {
|
||||
case WorkspacePlanStatuses.Valid:
|
||||
await upsertUnpaidWorkspacePlanFactory({ db })({
|
||||
workspacePlan: { workspaceId, status, name, createdAt }
|
||||
})
|
||||
case WorkspacePlanStatuses.CancelationScheduled:
|
||||
case WorkspacePlanStatuses.Canceled:
|
||||
case WorkspacePlanStatuses.Expired:
|
||||
case WorkspacePlanStatuses.PaymentFailed:
|
||||
case WorkspacePlanStatuses.Trial:
|
||||
throw new InvalidWorkspacePlanStatus()
|
||||
default:
|
||||
throwUncoveredError(status)
|
||||
}
|
||||
default:
|
||||
throwUncoveredError(name)
|
||||
}
|
||||
}
|
||||
},
|
||||
WorkspaceMutations: {
|
||||
create: async (_parent, args, context) => {
|
||||
const { name, description, defaultLogoIndex, logo, slug } = args.input
|
||||
@@ -952,10 +1031,9 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
return workspace?.role || null
|
||||
},
|
||||
team: async (parent, args) => {
|
||||
const team = await getPaginatedWorkspaceTeamFactory({
|
||||
getWorkspaceCollaborators: getWorkspaceCollaboratorsFactory({ db }),
|
||||
getWorkspaceCollaboratorsTotalCount:
|
||||
getWorkspaceCollaboratorsTotalCountFactory({ db })
|
||||
const team = await getPaginatedItemsFactory({
|
||||
getItems: getWorkspaceCollaboratorsFactory({ db }),
|
||||
getTotalCount: getWorkspaceCollaboratorsTotalCountFactory({ db })
|
||||
})({
|
||||
workspaceId: parent.id,
|
||||
filter: removeNullOrUndefinedKeys(args?.filter || {}),
|
||||
@@ -1241,8 +1319,15 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
}
|
||||
},
|
||||
AdminQueries: {
|
||||
workspaceList: async () => {
|
||||
throw new WorkspacesNotYetImplementedError()
|
||||
workspaceList: async (_parent, args) => {
|
||||
return await getPaginatedItemsFactory({
|
||||
getItems: queryWorkspacesFactory({ db }),
|
||||
getTotalCount: countWorkspacesFactory({ db })
|
||||
})({
|
||||
limit: args.limit,
|
||||
cursor: args.cursor,
|
||||
filter: { search: args.query ?? undefined }
|
||||
})
|
||||
}
|
||||
},
|
||||
LimitedUser: {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
CountDomainsByWorkspaceId,
|
||||
CountWorkspaceRoleWithOptionalProjectRole,
|
||||
CountWorkspaces,
|
||||
DeleteWorkspace,
|
||||
DeleteWorkspaceDomain,
|
||||
DeleteWorkspaceRole,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
GetWorkspaceRolesForUser,
|
||||
GetWorkspaceWithDomains,
|
||||
GetWorkspaces,
|
||||
QueryWorkspaces,
|
||||
StoreWorkspaceDomain,
|
||||
UpsertWorkspace,
|
||||
UpsertWorkspaceCreationState,
|
||||
@@ -174,6 +176,43 @@ export const getWorkspaceBySlugFactory =
|
||||
return workspace || null
|
||||
}
|
||||
|
||||
const buildWorkspacesQuery = ({ db, search }: { db: Knex; search?: string }) => {
|
||||
const query = tables.workspaces(db)
|
||||
|
||||
if (search) {
|
||||
query.andWhere((builder) => {
|
||||
builder
|
||||
.where('name', 'ILIKE', `%${search}%`)
|
||||
.orWhere('slug', 'ILIKE', `%${search}%`)
|
||||
})
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
export const queryWorkspacesFactory =
|
||||
({ db }: { db: Knex }): QueryWorkspaces =>
|
||||
async ({ limit, cursor, filter }) => {
|
||||
const query = buildWorkspacesQuery({ db, search: filter?.search })
|
||||
.select()
|
||||
.orderBy('createdAt', 'desc')
|
||||
.limit(limit)
|
||||
|
||||
if (cursor) {
|
||||
query.andWhere('createdAt', '<', cursor)
|
||||
}
|
||||
return await query
|
||||
}
|
||||
|
||||
export const countWorkspacesFactory =
|
||||
({ db }: { db: Knex }): CountWorkspaces =>
|
||||
async ({ filter }) => {
|
||||
const query = buildWorkspacesQuery({ db, search: filter?.search })
|
||||
|
||||
const [res] = await query.count()
|
||||
const count = parseInt(res.count.toString())
|
||||
return count
|
||||
}
|
||||
|
||||
export const upsertWorkspaceFactory =
|
||||
({ db }: { db: Knex }): UpsertWorkspace =>
|
||||
async ({ workspace }) => {
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
} from '@/modules/core/domain/streams/operations'
|
||||
import { ProjectNotFoundError } from '@/modules/core/errors/projects'
|
||||
import { WorkspaceProjectCreateInput } from '@/test/graphql/generated/graphql'
|
||||
import { getDb } from '@/modules/multiregion/dbSelector'
|
||||
import { getDb } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { createNewProjectFactory } from '@/modules/core/services/projects'
|
||||
import {
|
||||
deleteProjectFactory,
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operations'
|
||||
import {
|
||||
decodeIsoDateCursor,
|
||||
encodeIsoDateCursor
|
||||
} from '@/modules/shared/helpers/graphqlHelper'
|
||||
import {
|
||||
GetUserDiscoverableWorkspaces,
|
||||
GetWorkspace,
|
||||
GetWorkspaceCollaborators,
|
||||
GetWorkspaceCollaboratorsArgs,
|
||||
GetWorkspaceCollaboratorsTotalCount,
|
||||
GetWorkspaceRolesForUser
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceTeam } from '@/modules/workspaces/domain/types'
|
||||
import { Workspace } from '@/modules/workspacesCore/domain/types'
|
||||
import { chunk, isNull } from 'lodash'
|
||||
|
||||
@@ -78,38 +70,3 @@ export const getWorkspacesForUserFactory =
|
||||
|
||||
return workspaces
|
||||
}
|
||||
|
||||
type WorkspaceTeamCollection = {
|
||||
items: WorkspaceTeam
|
||||
cursor: string | null
|
||||
totalCount: number
|
||||
}
|
||||
|
||||
export const getPaginatedWorkspaceTeamFactory =
|
||||
({
|
||||
getWorkspaceCollaborators,
|
||||
getWorkspaceCollaboratorsTotalCount
|
||||
}: {
|
||||
getWorkspaceCollaborators: GetWorkspaceCollaborators
|
||||
getWorkspaceCollaboratorsTotalCount: GetWorkspaceCollaboratorsTotalCount
|
||||
}) =>
|
||||
async (args: GetWorkspaceCollaboratorsArgs): Promise<WorkspaceTeamCollection> => {
|
||||
const maybeDecodedCursor = args.cursor ? decodeIsoDateCursor(args.cursor) : null
|
||||
const items = await getWorkspaceCollaborators({
|
||||
...args,
|
||||
cursor: maybeDecodedCursor ?? undefined
|
||||
})
|
||||
const totalCount = await getWorkspaceCollaboratorsTotalCount(args)
|
||||
|
||||
let cursor = null
|
||||
if (items.length === args.limit) {
|
||||
const lastItem = items.at(-1)
|
||||
cursor = lastItem ? encodeIsoDateCursor(lastItem.createdAt) : null
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
cursor,
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ import {
|
||||
getDefaultRegionFactory,
|
||||
upsertRegionAssignmentFactory
|
||||
} from '@/modules/workspaces/repositories/regions'
|
||||
import { getDb } from '@/modules/multiregion/dbSelector'
|
||||
import { getDb } from '@/modules/multiregion/utils/dbSelector'
|
||||
import { WorkspacePlan } from '@/modules/gatekeeper/domain/billing'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
Reference in New Issue
Block a user