From 5c004f85b1cd2649cc4ebaef12e9cb096cb4ca4d Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Fri, 4 Apr 2025 10:51:46 +0200 Subject: [PATCH] chore(gatekeeper): get workspace plan repository and dataloader function --- .../modules/gatekeeper/domain/billing.ts | 4 ++ .../gatekeeper/graph/dataloaders/index.ts | 19 ++++++ .../gatekeeper/repositories/billing.ts | 12 +++- .../repositories/workspacePlan.spec.ts | 66 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 packages/server/modules/gatekeeper/tests/intergration/repositories/workspacePlan.spec.ts diff --git a/packages/server/modules/gatekeeper/domain/billing.ts b/packages/server/modules/gatekeeper/domain/billing.ts index 209a80d38..9ee912983 100644 --- a/packages/server/modules/gatekeeper/domain/billing.ts +++ b/packages/server/modules/gatekeeper/domain/billing.ts @@ -20,6 +20,10 @@ export type GetWorkspacePlan = (args: { workspaceId: string }) => Promise +export type GetWorkspacePlansByWorkspaceId = (args: { + workspaceIds: string[] +}) => Promise> + export type GetWorkspaceWithPlan = (args: { workspaceId: string }) => Promise }>> diff --git a/packages/server/modules/gatekeeper/graph/dataloaders/index.ts b/packages/server/modules/gatekeeper/graph/dataloaders/index.ts index 8ad08bb12..0cc49a1ec 100644 --- a/packages/server/modules/gatekeeper/graph/dataloaders/index.ts +++ b/packages/server/modules/gatekeeper/graph/dataloaders/index.ts @@ -1,10 +1,12 @@ import { WorkspaceSeat } from '@/modules/gatekeeper/domain/billing' +import { getWorkspacePlansByWorkspaceIdFactory } from '@/modules/gatekeeper/repositories/billing' import { getProjectsUsersSeatsFactory, getWorkspacesUsersSeatsFactory } from '@/modules/gatekeeper/repositories/workspaceSeat' import { getFeatureFlags } from '@/modules/shared/helpers/envHelper' import { defineRequestDataloaders } from '@/modules/shared/helpers/graphqlHelper' +import { WorkspacePlan } from '@speckle/shared' const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags() @@ -17,6 +19,7 @@ const dataLoadersDefinition = defineRequestDataloaders( ({ createLoader, deps: { db } }) => { const getWorkspacesUsersSeats = getWorkspacesUsersSeatsFactory({ db }) const getProjectsUsersSeats = getProjectsUsersSeatsFactory({ db }) + const getWorkspacePlansByWorkspaceId = getWorkspacePlansByWorkspaceIdFactory({ db }) return { gatekeeper: { @@ -55,6 +58,22 @@ const dataLoadersDefinition = defineRequestDataloaders( { cacheKeyFn: ({ projectId, userId }) => `${projectId}-${userId}` } + ), + getWorkspacePlan: createLoader< + { workspaceId: string }, + WorkspacePlan | null, + string + >( + async (requests) => { + const results = await getWorkspacePlansByWorkspaceId({ + workspaceIds: requests.map((request) => request.workspaceId) + }) + + return requests.map(({ workspaceId }) => results[workspaceId] || null) + }, + { + cacheKeyFn: ({ workspaceId }) => workspaceId + } ) } } diff --git a/packages/server/modules/gatekeeper/repositories/billing.ts b/packages/server/modules/gatekeeper/repositories/billing.ts index 7ce5e4b0a..f740fe8b0 100644 --- a/packages/server/modules/gatekeeper/repositories/billing.ts +++ b/packages/server/modules/gatekeeper/repositories/billing.ts @@ -16,7 +16,8 @@ import { GetWorkspaceSubscriptions, UpsertTrialWorkspacePlan, UpsertUnpaidWorkspacePlan, - GetWorkspaceWithPlan + GetWorkspaceWithPlan, + GetWorkspacePlansByWorkspaceId } from '@/modules/gatekeeper/domain/billing' import { ChangeExpiredTrialWorkspacePlanStatuses, @@ -93,6 +94,15 @@ export const getWorkspacePlanFactory = return workspacePlan ?? null } +export const getWorkspacePlansByWorkspaceIdFactory = + ({ db }: { db: Knex }): GetWorkspacePlansByWorkspaceId => + async ({ workspaceIds }) => { + const results = await tables + .workspacePlans(db) + .whereIn(WorkspacePlans.col.workspaceId, workspaceIds) + return results.reduce((acc, curr) => ({ ...acc, [curr.workspaceId]: curr }), {}) + } + export const upsertWorkspacePlanFactory = ({ db }: { db: Knex }): UpsertWorkspacePlan => async ({ workspacePlan }) => { diff --git a/packages/server/modules/gatekeeper/tests/intergration/repositories/workspacePlan.spec.ts b/packages/server/modules/gatekeeper/tests/intergration/repositories/workspacePlan.spec.ts new file mode 100644 index 000000000..0a4c0b782 --- /dev/null +++ b/packages/server/modules/gatekeeper/tests/intergration/repositories/workspacePlan.spec.ts @@ -0,0 +1,66 @@ +import { db } from '@/db/knex' +import { createRandomString } from '@/modules/core/helpers/testHelpers' +import { + getWorkspacePlansByWorkspaceIdFactory, + upsertWorkspacePlanFactory +} from '@/modules/gatekeeper/repositories/billing' +import { createTestWorkspace } from '@/modules/workspaces/tests/helpers/creation' +import { createTestUser } from '@/test/authHelper' +import { PaidWorkspacePlans, PaidWorkspacePlanStatuses } from '@speckle/shared' +import { expect } from 'chai' + +describe('Module @gatekeeper', () => { + const upsertWorkspacePlan = upsertWorkspacePlanFactory({ db }) + describe('Repositories WorkspacePlan', () => { + describe('getWorkspacePlansByWorkspaceIdFactory should return a function that, ', () => { + const getWorkspacePlansByWorkspaceId = getWorkspacePlansByWorkspaceIdFactory({ + db + }) + it('should return a map of workspacePlans by their workspaceId', async () => { + const now = new Date() + const user = await createTestUser() + const workspace1 = { + id: '', + name: createRandomString(), + ownerId: user.id + } + await createTestWorkspace(workspace1, user) + const plan1 = { + workspaceId: workspace1.id, + name: PaidWorkspacePlans.Team, + createdAt: now, + status: PaidWorkspacePlanStatuses.Valid + } + await upsertWorkspacePlan({ + workspacePlan: plan1 + }) + const workspace2 = { + id: '', + name: createRandomString(), + ownerId: user.id + } + await createTestWorkspace(workspace2, user) + const plan2 = { + workspaceId: workspace2.id, + name: PaidWorkspacePlans.Team, + createdAt: now, + status: PaidWorkspacePlanStatuses.Valid + } + await upsertWorkspacePlan({ + workspacePlan: plan2 + }) + + const results = await getWorkspacePlansByWorkspaceId({ + workspaceIds: [workspace1.id, workspace2.id] + }) + + for (const [workspaceId, workspacePlan] of Object.entries(results)) { + const { createdAt, ...plan } = workspacePlan + expect(createdAt).to.not.eq(null) + expect(plan.workspaceId).to.eq(workspaceId) + expect(plan).to.deep.eq(plan) + } + }) + }) + }) +})