Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alessandro/web 1660 workspace cost items subtotal and currency #2793

Merged
25 changes: 24 additions & 1 deletion packages/frontend-2/lib/common/generated/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,12 @@ export type CreateVersionInput = {
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};

export enum Currency {
Eur = 'EUR',
Gbp = 'GBP',
Usd = 'USD'
}

export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
Expand Down Expand Up @@ -3894,7 +3900,8 @@ export type WorkspaceTeamArgs = {

export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
versionsCount?: Maybe<WorkspaceVersionsCount>;
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};

export type WorkspaceCollaborator = {
Expand All @@ -3911,6 +3918,22 @@ export type WorkspaceCollection = {
totalCount: Scalars['Int']['output'];
};

export type WorkspaceCost = {
__typename?: 'WorkspaceCost';
/** Currency of the price */
currency: Currency;
items: Array<WorkspaceCostItem>;
/** Estimated cost of the workspace with no discount applied */
subTotal: Scalars['Float']['output'];
};

export type WorkspaceCostItem = {
__typename?: 'WorkspaceCostItem';
cost: Scalars['Float']['output'];
count: Scalars['Int']['output'];
name: Scalars['String']['output'];
};

export type WorkspaceCreateInput = {
defaultLogoIndex?: InputMaybe<Scalars['Int']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,33 @@ type WorkspaceVersionsCount {
max: Int!
}

enum Currency {
GBP
USD
EUR
}

type WorkspaceCostItem {
count: Int!
name: String!
cost: Float!
}

type WorkspaceCost {
"""
Estimated cost of the workspace with no discount applied
"""
subTotal: Float!
"""
Currency of the price
"""
currency: Currency!
items: [WorkspaceCostItem!]!
}

type WorkspaceBilling {
versionsCount: WorkspaceVersionsCount
versionsCount: WorkspaceVersionsCount!
cost: WorkspaceCost!
}

type Workspace {
Expand Down
2 changes: 1 addition & 1 deletion packages/server/modules/core/events/projectsEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const ProjectEvents = {
export type ProjectEvents = (typeof ProjectEvents)[keyof typeof ProjectEvents]

export type ProjectEventsPayloads = {
[ProjectEvents.Created]: { project: StreamRecord }
[ProjectEvents.Created]: { project: StreamRecord; ownerId: string }
}

const { emit, listen } = initializeModuleEventEmitter<ProjectEventsPayloads>({
Expand Down
49 changes: 47 additions & 2 deletions packages/server/modules/core/graph/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,12 @@ export type CreateVersionInput = {
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};

export enum Currency {
Eur = 'EUR',
Gbp = 'GBP',
Usd = 'USD'
}

export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
Expand Down Expand Up @@ -3908,7 +3914,8 @@ export type WorkspaceTeamArgs = {

export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
versionsCount?: Maybe<WorkspaceVersionsCount>;
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};

export type WorkspaceCollaborator = {
Expand All @@ -3925,6 +3932,22 @@ export type WorkspaceCollection = {
totalCount: Scalars['Int']['output'];
};

export type WorkspaceCost = {
__typename?: 'WorkspaceCost';
/** Currency of the price */
currency: Currency;
items: Array<WorkspaceCostItem>;
/** Estimated cost of the workspace with no discount applied */
subTotal: Scalars['Float']['output'];
};

export type WorkspaceCostItem = {
__typename?: 'WorkspaceCostItem';
cost: Scalars['Float']['output'];
count: Scalars['Int']['output'];
name: Scalars['String']['output'];
};

export type WorkspaceCreateInput = {
defaultLogoIndex?: InputMaybe<Scalars['Int']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
Expand Down Expand Up @@ -4270,6 +4293,7 @@ export type ResolversTypes = {
CreateModelInput: CreateModelInput;
CreateUserEmailInput: CreateUserEmailInput;
CreateVersionInput: CreateVersionInput;
Currency: Currency;
DateTime: ResolverTypeWrapper<Scalars['DateTime']['output']>;
DeleteModelInput: DeleteModelInput;
DeleteUserEmailInput: DeleteUserEmailInput;
Expand Down Expand Up @@ -4428,6 +4452,8 @@ export type ResolversTypes = {
WorkspaceBilling: ResolverTypeWrapper<WorkspaceBilling>;
WorkspaceCollaborator: ResolverTypeWrapper<WorkspaceCollaboratorGraphQLReturn>;
WorkspaceCollection: ResolverTypeWrapper<Omit<WorkspaceCollection, 'items'> & { items: Array<ResolversTypes['Workspace']> }>;
WorkspaceCost: ResolverTypeWrapper<WorkspaceCost>;
WorkspaceCostItem: ResolverTypeWrapper<WorkspaceCostItem>;
WorkspaceCreateInput: WorkspaceCreateInput;
WorkspaceDomain: ResolverTypeWrapper<WorkspaceDomain>;
WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput;
Expand Down Expand Up @@ -4662,6 +4688,8 @@ export type ResolversParentTypes = {
WorkspaceBilling: WorkspaceBilling;
WorkspaceCollaborator: WorkspaceCollaboratorGraphQLReturn;
WorkspaceCollection: Omit<WorkspaceCollection, 'items'> & { items: Array<ResolversParentTypes['Workspace']> };
WorkspaceCost: WorkspaceCost;
WorkspaceCostItem: WorkspaceCostItem;
WorkspaceCreateInput: WorkspaceCreateInput;
WorkspaceDomain: WorkspaceDomain;
WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput;
Expand Down Expand Up @@ -6034,7 +6062,8 @@ export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends
};

export type WorkspaceBillingResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceBilling'] = ResolversParentTypes['WorkspaceBilling']> = {
versionsCount?: Resolver<Maybe<ResolversTypes['WorkspaceVersionsCount']>, ParentType, ContextType>;
cost?: Resolver<ResolversTypes['WorkspaceCost'], ParentType, ContextType>;
versionsCount?: Resolver<ResolversTypes['WorkspaceVersionsCount'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

Expand All @@ -6052,6 +6081,20 @@ export type WorkspaceCollectionResolvers<ContextType = GraphQLContext, ParentTyp
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type WorkspaceCostResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceCost'] = ResolversParentTypes['WorkspaceCost']> = {
currency?: Resolver<ResolversTypes['Currency'], ParentType, ContextType>;
items?: Resolver<Array<ResolversTypes['WorkspaceCostItem']>, ParentType, ContextType>;
subTotal?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type WorkspaceCostItemResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceCostItem'] = ResolversParentTypes['WorkspaceCostItem']> = {
cost?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
count?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type WorkspaceDomainResolvers<ContextType = GraphQLContext, ParentType extends ResolversParentTypes['WorkspaceDomain'] = ResolversParentTypes['WorkspaceDomain']> = {
domain?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
Expand Down Expand Up @@ -6218,6 +6261,8 @@ export type Resolvers<ContextType = GraphQLContext> = {
WorkspaceBilling?: WorkspaceBillingResolvers<ContextType>;
WorkspaceCollaborator?: WorkspaceCollaboratorResolvers<ContextType>;
WorkspaceCollection?: WorkspaceCollectionResolvers<ContextType>;
WorkspaceCost?: WorkspaceCostResolvers<ContextType>;
WorkspaceCostItem?: WorkspaceCostItemResolvers<ContextType>;
WorkspaceDomain?: WorkspaceDomainResolvers<ContextType>;
WorkspaceInviteMutations?: WorkspaceInviteMutationsResolvers<ContextType>;
WorkspaceMutations?: WorkspaceMutationsResolvers<ContextType>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export async function createStreamReturnRecord(
})
}

await ProjectsEmitter.emit(ProjectEvents.Created, { project: stream })
await ProjectsEmitter.emit(ProjectEvents.Created, { project: stream, ownerId })

return stream
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,12 @@ export type CreateVersionInput = {
totalChildrenCount?: InputMaybe<Scalars['Int']['input']>;
};

export enum Currency {
Eur = 'EUR',
Gbp = 'GBP',
Usd = 'USD'
}

export type DeleteModelInput = {
id: Scalars['ID']['input'];
projectId: Scalars['ID']['input'];
Expand Down Expand Up @@ -3897,7 +3903,8 @@ export type WorkspaceTeamArgs = {

export type WorkspaceBilling = {
__typename?: 'WorkspaceBilling';
versionsCount?: Maybe<WorkspaceVersionsCount>;
cost: WorkspaceCost;
versionsCount: WorkspaceVersionsCount;
};

export type WorkspaceCollaborator = {
Expand All @@ -3914,6 +3921,22 @@ export type WorkspaceCollection = {
totalCount: Scalars['Int']['output'];
};

export type WorkspaceCost = {
__typename?: 'WorkspaceCost';
/** Currency of the price */
currency: Currency;
items: Array<WorkspaceCostItem>;
/** Estimated cost of the workspace with no discount applied */
subTotal: Scalars['Float']['output'];
};

export type WorkspaceCostItem = {
__typename?: 'WorkspaceCostItem';
cost: Scalars['Float']['output'];
count: Scalars['Int']['output'];
name: Scalars['String']['output'];
};

export type WorkspaceCreateInput = {
defaultLogoIndex?: InputMaybe<Scalars['Int']['input']>;
description?: InputMaybe<Scalars['String']['input']>;
Expand Down
5 changes: 5 additions & 0 deletions packages/server/modules/workspaces/domain/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export const WorkspaceInviteResourceType = 'workspace'

export const WORKSPACE_COST_ADMIN = 70
export const WORKSPACE_COST_MEMBER = 50
export const WORKSPACE_COST_GUEST = 10
export const WORKSPACE_COST_VIEWER = 0
12 changes: 8 additions & 4 deletions packages/server/modules/workspaces/domain/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
WorkspaceWithOptionalRole
} from '@/modules/workspacesCore/domain/types'
import { EventBusPayloads } from '@/modules/shared/services/eventBus'
import { WorkspaceRoles } from '@speckle/shared'
import { StreamRoles, WorkspaceRoles } from '@speckle/shared'
import { UserWithRole } from '@/modules/core/repositories/users'

/** Workspace */
Expand Down Expand Up @@ -147,8 +147,12 @@ export type EmitWorkspaceEvent = <TEvent extends WorkspaceEvents>(args: {
payload: EventBusPayloads[TEvent]
}) => Promise<unknown[]>

export type CountProjectsVersionsByWorkspaceId = ({
workspaceId
}: {
export type CountProjectsVersionsByWorkspaceId = (args: {
workspaceId: string
}) => Promise<number>

export type CountWorkspaceRoleWithOptionalProjectRole = (args: {
workspaceId: string
workspaceRole: WorkspaceRoles
projectRole?: StreamRoles | undefined
}) => Promise<number>
2 changes: 2 additions & 0 deletions packages/server/modules/workspaces/events/eventListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const onProjectCreatedFactory =

await Promise.all(
workspaceMembers.map(({ userId, role: workspaceRole }) => {
// we do not need to assign new roles to the project owner
if (userId === payload.ownerId) return
// Guests do not get roles on project create
if (workspaceRole === Roles.Workspace.Guest) return

Expand Down
27 changes: 23 additions & 4 deletions packages/server/modules/workspaces/graph/resolvers/workspaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { db } from '@/db/knex'
import { Resolvers } from '@/modules/core/graph/generated/graphql'
import {
ResolverTypeWrapper,
Resolvers,
WorkspaceBilling
} from '@/modules/core/graph/generated/graphql'
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
import {
getStream,
Expand Down Expand Up @@ -66,7 +70,8 @@ import {
getWorkspaceDomainsFactory,
getUserDiscoverableWorkspacesFactory,
getWorkspaceWithDomainsFactory,
countProjectsVersionsByWorkspaceIdFactory
countProjectsVersionsByWorkspaceIdFactory,
countWorkspaceRoleWithOptionalProjectRoleFactory
} from '@/modules/workspaces/repositories/workspaces'
import {
buildWorkspaceInviteEmailContentsFactory,
Expand Down Expand Up @@ -109,6 +114,10 @@ import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userE
import { requestNewEmailVerification } from '@/modules/emails/services/verification/request'
import { Workspace } from '@/modules/workspacesCore/domain/types'
import { WORKSPACE_MAX_PROJECTS_VERSIONS } from '@/modules/gatekeeper/domain/constants'
import {
getWorkspaceCostFactory,
getWorkspaceCostItemsFactory
} from '@/modules/workspaces/services/cost'
import { isUserWorkspaceDomainPolicyCompliantFactory } from '@/modules/workspaces/services/domains'

const buildCreateAndSendServerOrProjectInvite = () =>
Expand Down Expand Up @@ -639,16 +648,26 @@ export = FF_WORKSPACES_MODULE_ENABLED
domains: async (parent) => {
return await getWorkspaceDomainsFactory({ db })({ workspaceIds: [parent.id] })
},
billing: (parent) => ({ parent })
billing: (parent) =>
({ parent } as { parent: Workspace } & ResolverTypeWrapper<WorkspaceBilling>)
},
WorkspaceBilling: {
versionsCount: async (parent) => {
const workspaceId = (parent as unknown as { parent: Workspace }).parent.id
return {
current: await countProjectsVersionsByWorkspaceIdFactory({ db })({
workspaceId: (parent as { parent: Workspace }).parent.id
workspaceId
}),
max: WORKSPACE_MAX_PROJECTS_VERSIONS
}
},
cost: async (parent) => {
const workspaceId = (parent as unknown as { parent: Workspace }).parent.id
return getWorkspaceCostFactory({
getWorkspaceCostItems: getWorkspaceCostItemsFactory({
countRole: countWorkspaceRoleWithOptionalProjectRoleFactory({ db })
})
})({ workspaceId })
}
},
WorkspaceCollaborator: {
Expand Down
Loading