gergo/workspaceDefaultPlan (#3561)
* feat(gatekeeper): create workspaces with trial plan by default * feat(gatekeeper): default to starter trial plan * fix(eventBus): fix tests
This commit is contained in:
@@ -52,6 +52,7 @@ const command: CommandModule<
|
||||
|
||||
await upsertPaidWorkspacePlanFactory({ db })({
|
||||
workspacePlan: {
|
||||
createdAt: new Date(),
|
||||
workspaceId: workspace.id,
|
||||
name: args.plan,
|
||||
status: args.status
|
||||
|
||||
@@ -26,6 +26,7 @@ export type PlanStatuses =
|
||||
|
||||
type BaseWorkspacePlan = {
|
||||
workspaceId: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
export type PaidWorkspacePlan = BaseWorkspacePlan & {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { reconcileWorkspaceSubscriptionFactory } from '@/modules/gatekeeper/clients/stripe'
|
||||
import {
|
||||
getWorkspacePlanFactory,
|
||||
getWorkspaceSubscriptionFactory
|
||||
getWorkspaceSubscriptionFactory,
|
||||
upsertTrialWorkspacePlanFactory
|
||||
} from '@/modules/gatekeeper/repositories/billing'
|
||||
import { addWorkspaceSubscriptionSeatIfNeededFactory } from '@/modules/gatekeeper/services/subscriptions'
|
||||
import {
|
||||
@@ -33,6 +34,16 @@ export const initializeEventListenersFactory =
|
||||
})
|
||||
|
||||
await addWorkspaceSubscriptionSeatIfNeeded(payload)
|
||||
}),
|
||||
eventBus.listen(WorkspaceEvents.Created, async ({ payload }) => {
|
||||
await upsertTrialWorkspacePlanFactory({ db })({
|
||||
workspacePlan: {
|
||||
name: 'starter',
|
||||
status: 'trial',
|
||||
workspaceId: payload.id,
|
||||
createdAt: new Date()
|
||||
}
|
||||
})
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ const scheduleWorkspaceSubscriptionDownscale = () => {
|
||||
'WorkspaceSubscriptionDownscale',
|
||||
async () => {
|
||||
await manageSubscriptionDownscale()
|
||||
// await cleanOrphanedWebhookConfigs()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Knex } from 'knex'
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('workspace_plans', (table) => {
|
||||
table
|
||||
.timestamp('createdAt', { precision: 3, useTz: true })
|
||||
.defaultTo(knex.fn.now())
|
||||
.notNullable()
|
||||
})
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('workspace_plans', (table) => {
|
||||
table.dropColumn('createdAt')
|
||||
})
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
GetWorkspaceCheckoutSession,
|
||||
GetWorkspaceSubscription,
|
||||
GetWorkspaceSubscriptionBySubscriptionId,
|
||||
GetWorkspaceSubscriptions
|
||||
GetWorkspaceSubscriptions,
|
||||
UpsertTrialWorkspacePlan
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
import { Knex } from 'knex'
|
||||
|
||||
@@ -54,6 +55,12 @@ export const upsertPaidWorkspacePlanFactory = ({
|
||||
db: Knex
|
||||
}): UpsertPaidWorkspacePlan => upsertWorkspacePlanFactory({ db })
|
||||
|
||||
export const upsertTrialWorkspacePlanFactory = ({
|
||||
db
|
||||
}: {
|
||||
db: Knex
|
||||
}): UpsertTrialWorkspacePlan => upsertWorkspacePlanFactory({ db })
|
||||
|
||||
export const saveCheckoutSessionFactory =
|
||||
({ db }: { db: Knex }): SaveCheckoutSession =>
|
||||
async ({ checkoutSession }) => {
|
||||
|
||||
@@ -167,6 +167,7 @@ export const completeCheckoutSessionFactory =
|
||||
// a plan determines the workspace feature set
|
||||
await upsertPaidWorkspacePlan({
|
||||
workspacePlan: {
|
||||
createdAt: new Date(),
|
||||
workspaceId: checkoutSession.workspaceId,
|
||||
name: checkoutSession.workspacePlan,
|
||||
status: 'valid'
|
||||
|
||||
@@ -52,7 +52,8 @@ describe('billing repositories @gatekeeper', () => {
|
||||
const workspacePlan = {
|
||||
name: 'business',
|
||||
status: 'paymentFailed',
|
||||
workspaceId
|
||||
workspaceId,
|
||||
createdAt: new Date()
|
||||
} as const
|
||||
await upsertPaidWorkspacePlan({
|
||||
workspacePlan
|
||||
@@ -67,6 +68,7 @@ describe('billing repositories @gatekeeper', () => {
|
||||
const workspacePlan = {
|
||||
name: 'business',
|
||||
status: 'paymentFailed',
|
||||
createdAt: new Date(),
|
||||
workspaceId
|
||||
} as const
|
||||
await upsertPaidWorkspacePlan({
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
PaidWorkspacePlans,
|
||||
WorkspacePlanBillingIntervals
|
||||
} from '@/modules/gatekeeper/domain/workspacePricing'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
describe('checkout @gatekeeper', () => {
|
||||
describe('startCheckoutSessionFactory creates a function, that', () => {
|
||||
@@ -30,6 +31,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'plus',
|
||||
status: 'valid',
|
||||
createdAt: new Date(),
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: () => {
|
||||
@@ -63,6 +65,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'plus',
|
||||
status: 'paymentFailed',
|
||||
createdAt: new Date(),
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: () => {
|
||||
@@ -96,6 +99,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
status: 'trial',
|
||||
createdAt: new Date(),
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => ({
|
||||
@@ -139,6 +143,8 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
status: 'trial',
|
||||
createdAt: new Date(),
|
||||
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => ({
|
||||
@@ -265,6 +271,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
workspaceId,
|
||||
name: 'starter',
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => null,
|
||||
@@ -315,6 +322,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
workspaceId,
|
||||
name: 'starter',
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => existingCheckoutSession!,
|
||||
@@ -356,6 +364,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
workspaceId,
|
||||
name: 'starter',
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => existingCheckoutSession!,
|
||||
@@ -396,6 +405,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
workspaceId,
|
||||
name: 'starter',
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => existingCheckoutSession!,
|
||||
@@ -448,6 +458,7 @@ describe('checkout @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'plus',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'canceled'
|
||||
}),
|
||||
getWorkspaceCheckoutSession: async () => existingCheckoutSession!,
|
||||
@@ -578,7 +589,7 @@ describe('checkout @gatekeeper', () => {
|
||||
})({ sessionId, subscriptionId })
|
||||
|
||||
expect(storedCheckoutSession.paymentStatus).to.equal('paid')
|
||||
expect(storedWorkspacePlan).to.deep.equal({
|
||||
expect(omit(storedWorkspacePlan, 'createdAt')).to.deep.equal({
|
||||
workspaceId,
|
||||
name: storedCheckoutSession.workspacePlan,
|
||||
status: 'valid'
|
||||
|
||||
@@ -73,7 +73,12 @@ describe('subscriptions @gatekeeper', () => {
|
||||
subscriptionData,
|
||||
workspaceId
|
||||
}),
|
||||
getWorkspacePlan: async () => ({ name, workspaceId, status: 'valid' }),
|
||||
getWorkspacePlan: async () => ({
|
||||
name,
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
@@ -104,6 +109,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async ({ workspaceSubscription }) => {
|
||||
@@ -144,6 +150,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async ({ workspaceSubscription }) => {
|
||||
@@ -180,6 +187,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async ({ workspaceSubscription }) => {
|
||||
@@ -219,6 +227,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async ({ workspaceSubscription }) => {
|
||||
@@ -255,6 +264,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'trial'
|
||||
}),
|
||||
upsertWorkspaceSubscription: async () => {
|
||||
@@ -302,6 +312,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'unlimited',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
getWorkspaceSubscription: async () => null,
|
||||
@@ -335,6 +346,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'unlimited',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
getWorkspaceSubscription: async () => workspaceSubscription,
|
||||
@@ -371,6 +383,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'plus',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'canceled'
|
||||
}),
|
||||
getWorkspaceSubscription: async () => workspaceSubscription,
|
||||
@@ -402,6 +415,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
const workspacePlan: WorkspacePlan = {
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}
|
||||
const priceId = cryptoRandomString({ length: 10 })
|
||||
@@ -463,6 +477,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
const workspacePlan: WorkspacePlan = {
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}
|
||||
const priceId = cryptoRandomString({ length: 10 })
|
||||
@@ -541,6 +556,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
const workspacePlan: WorkspacePlan = {
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}
|
||||
const roleCount = 10
|
||||
@@ -612,6 +628,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
const workspacePlan: WorkspacePlan = {
|
||||
name: 'starter',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}
|
||||
const roleCount = 1
|
||||
@@ -690,6 +707,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'unlimited',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
countWorkspaceRole: async () => {
|
||||
@@ -718,6 +736,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: 'plus',
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'canceled'
|
||||
}),
|
||||
countWorkspaceRole: async () => {
|
||||
@@ -753,6 +772,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: workspacePlanName,
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
countWorkspaceRole: async ({ workspaceRole }) => {
|
||||
@@ -807,6 +827,7 @@ describe('subscriptions @gatekeeper', () => {
|
||||
getWorkspacePlan: async () => ({
|
||||
name: workspacePlanName,
|
||||
workspaceId,
|
||||
createdAt: new Date(),
|
||||
status: 'valid'
|
||||
}),
|
||||
countWorkspaceRole: async ({ workspaceRole }) => {
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import { getEventBus, initializeEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events'
|
||||
import { Workspace } from '@/modules/workspacesCore/domain/types'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { expect } from 'chai'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
|
||||
const createFakeWorkspace = (): Omit<Workspace, 'domains'> => {
|
||||
return {
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
description: cryptoRandomString({ length: 10 }),
|
||||
logo: null,
|
||||
defaultLogoIndex: 0,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
defaultProjectRole: Roles.Stream.Contributor,
|
||||
domainBasedMembershipProtectionEnabled: false,
|
||||
discoverabilityEnabled: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Event Bus', () => {
|
||||
describe('initializeEventBus creates an event bus instance, that', () => {
|
||||
it('calls back all the listeners', async () => {
|
||||
@@ -106,69 +87,55 @@ describe('Event Bus', () => {
|
||||
const bus1 = getEventBus()
|
||||
const bus2 = getEventBus()
|
||||
|
||||
const workspaces: Workspace[] = []
|
||||
const payloads: string[] = []
|
||||
|
||||
bus1.listen(WorkspaceEvents.Created, ({ payload }) => {
|
||||
workspaces.push(payload)
|
||||
bus1.listen('test.string', ({ payload }) => {
|
||||
payloads.push(payload)
|
||||
})
|
||||
|
||||
bus2.listen(WorkspaceEvents.Created, ({ payload }) => {
|
||||
workspaces.push(payload)
|
||||
bus2.listen('test.string', ({ payload }) => {
|
||||
payloads.push(payload)
|
||||
})
|
||||
|
||||
const workspacePayload = {
|
||||
...createFakeWorkspace(),
|
||||
createdByUserId: cryptoRandomString({ length: 10 }),
|
||||
eventName: WorkspaceEvents.Created,
|
||||
domains: []
|
||||
}
|
||||
const payload = cryptoRandomString({ length: 1 })
|
||||
|
||||
await bus1.emit({
|
||||
eventName: WorkspaceEvents.Created,
|
||||
payload: { ...workspacePayload }
|
||||
eventName: 'test.string',
|
||||
payload
|
||||
})
|
||||
|
||||
expect(workspaces.length).to.equal(2)
|
||||
expect(workspaces).to.deep.equal([workspacePayload, workspacePayload])
|
||||
expect(payloads.length).to.equal(2)
|
||||
expect(payloads).to.deep.equal([payload, payload])
|
||||
})
|
||||
it('allows to subscribe to wildcard events', async () => {
|
||||
const eventBus = getEventBus()
|
||||
|
||||
const events: string[] = []
|
||||
|
||||
eventBus.listen('workspace.*', ({ payload, eventName }) => {
|
||||
eventBus.listen('test.*', ({ payload, eventName }) => {
|
||||
switch (eventName) {
|
||||
case 'workspace.created':
|
||||
events.push(payload.id)
|
||||
case 'test.string':
|
||||
events.push(payload)
|
||||
break
|
||||
case 'workspace.role-deleted':
|
||||
events.push(payload.userId)
|
||||
case 'test.number':
|
||||
events.push(`${payload}`)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
const workspace = createFakeWorkspace()
|
||||
const stringPayload = cryptoRandomString({ length: 10 })
|
||||
|
||||
await eventBus.emit({
|
||||
eventName: WorkspaceEvents.Created,
|
||||
payload: {
|
||||
...workspace,
|
||||
createdByUserId: cryptoRandomString({ length: 10 })
|
||||
}
|
||||
eventName: 'test.string',
|
||||
payload: stringPayload
|
||||
})
|
||||
|
||||
const workspaceAcl = {
|
||||
userId: cryptoRandomString({ length: 10 }),
|
||||
workspaceId: cryptoRandomString({ length: 10 }),
|
||||
role: Roles.Workspace.Member
|
||||
}
|
||||
|
||||
await eventBus.emit({
|
||||
eventName: WorkspaceEvents.RoleDeleted,
|
||||
payload: workspaceAcl
|
||||
eventName: 'test.number',
|
||||
payload: 999
|
||||
})
|
||||
|
||||
expect([workspace.id, workspaceAcl.userId]).to.deep.equal(events)
|
||||
expect([stringPayload, `${999}`]).to.deep.equal(events)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -134,6 +134,7 @@ export const createTestWorkspace = async (
|
||||
if (addPlan) {
|
||||
await upsertWorkspacePlan({
|
||||
workspacePlan: {
|
||||
createdAt: new Date(),
|
||||
workspaceId: newWorkspace.id,
|
||||
name: 'business',
|
||||
status: 'valid'
|
||||
|
||||
Reference in New Issue
Block a user