Skip to content

Commit

Permalink
feat(gatekeeper): show subscription seats
Browse files Browse the repository at this point in the history
  • Loading branch information
gjedlicska committed Dec 11, 2024
1 parent 229a19c commit cbd8757
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 1 deletion.
6 changes: 6 additions & 0 deletions packages/server/assets/gatekeeper/typedefs/gatekeeper.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ type WorkspacePlan {
createdAt: DateTime!
}

type WorkspaceSubscriptionSeats {
plan: Int!
guest: Int!
}

type WorkspaceSubscription {
createdAt: DateTime!
updatedAt: DateTime!
currentBillingCycleEnd: DateTime!
billingInterval: BillingInterval!
seats: WorkspaceSubscriptionSeats!
}

extend type Workspace {
Expand Down
17 changes: 17 additions & 0 deletions packages/server/modules/core/graph/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4574,9 +4574,16 @@ export type WorkspaceSubscription = {
billingInterval: BillingInterval;
createdAt: Scalars['DateTime']['output'];
currentBillingCycleEnd: Scalars['DateTime']['output'];
seats: WorkspaceSubscriptionSeats;
updatedAt: Scalars['DateTime']['output'];
};

export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
guest: Scalars['Int']['output'];
plan: Scalars['Int']['output'];
};

export type WorkspaceTeamFilter = {
/** Limit team members to provided role(s) */
roles?: InputMaybe<Array<Scalars['String']['input']>>;
Expand Down Expand Up @@ -4963,6 +4970,7 @@ export type ResolversTypes = {
WorkspaceSsoProvider: ResolverTypeWrapper<WorkspaceSsoProvider>;
WorkspaceSsoSession: ResolverTypeWrapper<WorkspaceSsoSession>;
WorkspaceSubscription: ResolverTypeWrapper<WorkspaceSubscription>;
WorkspaceSubscriptionSeats: ResolverTypeWrapper<WorkspaceSubscriptionSeats>;
WorkspaceTeamFilter: WorkspaceTeamFilter;
WorkspaceUpdateInput: WorkspaceUpdateInput;
WorkspaceUpdatedMessage: ResolverTypeWrapper<Omit<WorkspaceUpdatedMessage, 'workspace'> & { workspace: ResolversTypes['Workspace'] }>;
Expand Down Expand Up @@ -5228,6 +5236,7 @@ export type ResolversParentTypes = {
WorkspaceSsoProvider: WorkspaceSsoProvider;
WorkspaceSsoSession: WorkspaceSsoSession;
WorkspaceSubscription: WorkspaceSubscription;
WorkspaceSubscriptionSeats: WorkspaceSubscriptionSeats;
WorkspaceTeamFilter: WorkspaceTeamFilter;
WorkspaceUpdateInput: WorkspaceUpdateInput;
WorkspaceUpdatedMessage: Omit<WorkspaceUpdatedMessage, 'workspace'> & { workspace: ResolversParentTypes['Workspace'] };
Expand Down Expand Up @@ -6796,10 +6805,17 @@ export type WorkspaceSubscriptionResolvers<ContextType = GraphQLContext, ParentT
billingInterval?: Resolver<ResolversTypes['BillingInterval'], ParentType, ContextType>;
createdAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
currentBillingCycleEnd?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
seats?: Resolver<ResolversTypes['WorkspaceSubscriptionSeats'], ParentType, ContextType>;
updatedAt?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type WorkspaceSubscriptionSeatsResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceSubscriptionSeats'] = ResolversParentTypes['WorkspaceSubscriptionSeats']> = {
guest?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
plan?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type WorkspaceUpdatedMessageResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceUpdatedMessage'] = ResolversParentTypes['WorkspaceUpdatedMessage']> = {
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
workspace?: Resolver<ResolversTypes['Workspace'], ParentType, ContextType>;
Expand Down Expand Up @@ -6960,6 +6976,7 @@ export type Resolvers<ContextType = GraphQLContext> = {
WorkspaceSsoProvider?: WorkspaceSsoProviderResolvers<ContextType>;
WorkspaceSsoSession?: WorkspaceSsoSessionResolvers<ContextType>;
WorkspaceSubscription?: WorkspaceSubscriptionResolvers<ContextType>;
WorkspaceSubscriptionSeats?: WorkspaceSubscriptionSeatsResolvers<ContextType>;
WorkspaceUpdatedMessage?: WorkspaceUpdatedMessageResolvers<ContextType>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4555,9 +4555,16 @@ export type WorkspaceSubscription = {
billingInterval: BillingInterval;
createdAt: Scalars['DateTime']['output'];
currentBillingCycleEnd: Scalars['DateTime']['output'];
seats: WorkspaceSubscriptionSeats;
updatedAt: Scalars['DateTime']['output'];
};

export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
guest: Scalars['Int']['output'];
plan: Scalars['Int']['output'];
};

export type WorkspaceTeamFilter = {
/** Limit team members to provided role(s) */
roles?: InputMaybe<Array<Scalars['String']['input']>>;
Expand Down
17 changes: 17 additions & 0 deletions packages/server/modules/gatekeeper/domain/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ export const subscriptionData = z.object({
products: subscriptionProduct.array()
})

export const calculateSubscriptionSeats = ({
subscriptionData,
guestSeatProductId
}: {
subscriptionData: SubscriptionData
guestSeatProductId: string
}): { plan: number; guest: number } => {
const guestProduct = subscriptionData.products.find(
(p) => p.productId === guestSeatProductId
)

const planProduct = subscriptionData.products.find(
(p) => p.productId !== guestSeatProductId
)
return { guest: guestProduct?.quantity || 0, plan: planProduct?.quantity || 0 }
}

// this abstracts the stripe sub data
export type SubscriptionData = z.infer<typeof subscriptionData>

Expand Down
11 changes: 10 additions & 1 deletion packages/server/modules/gatekeeper/graph/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from '@/modules/gatekeeper/repositories/billing'
import { canWorkspaceAccessFeatureFactory } from '@/modules/gatekeeper/services/featureAuthorization'
import { upgradeWorkspaceSubscriptionFactory } from '@/modules/gatekeeper/services/subscriptions'
import { calculateSubscriptionSeats } from '@/modules/gatekeeper/domain/billing'

const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags()

Expand All @@ -47,7 +48,15 @@ export = FF_GATEKEEPER_MODULE_ENABLED
},
subscription: async (parent) => {
const workspaceId = parent.id
return await getWorkspaceSubscriptionFactory({ db })({ workspaceId })
const subscription = await getWorkspaceSubscriptionFactory({ db })({
workspaceId
})
if (!subscription) return subscription
const seats = calculateSubscriptionSeats({
subscriptionData: subscription.subscriptionData,
guestSeatProductId: getWorkspacePlanProductId({ workspacePlan: 'guest' })
})
return { ...subscription, seats }
},
customerPortalUrl: async (parent) => {
const workspaceId = parent.id
Expand Down
7 changes: 7 additions & 0 deletions packages/server/test/graphql/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4556,9 +4556,16 @@ export type WorkspaceSubscription = {
billingInterval: BillingInterval;
createdAt: Scalars['DateTime']['output'];
currentBillingCycleEnd: Scalars['DateTime']['output'];
seats: WorkspaceSubscriptionSeats;
updatedAt: Scalars['DateTime']['output'];
};

export type WorkspaceSubscriptionSeats = {
__typename?: 'WorkspaceSubscriptionSeats';
guest: Scalars['Int']['output'];
plan: Scalars['Int']['output'];
};

export type WorkspaceTeamFilter = {
/** Limit team members to provided role(s) */
roles?: InputMaybe<Array<Scalars['String']['input']>>;
Expand Down

0 comments on commit cbd8757

Please sign in to comment.