diff --git a/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts index 91fc285373..1a85aafe79 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/types/events.type.ts @@ -38,6 +38,14 @@ import { type WEBHOOK_RESPONSE_EVENT, type WebhookResponseTrackEvent, } from 'src/engine/core-modules/audit/utils/events/workspace-event/webhook/webhook-response'; +import { + type PAYMENT_RECEIVED_EVENT, + type PaymentReceivedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/workspace-event/billing/payment-received'; +import { + type WORKSPACE_CREATED_EVENT, + type WorkspaceCreatedTrackEvent, +} from 'src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created'; import { type WORKSPACE_ENTITY_CREATED_EVENT, type WorkspaceEntityCreatedTrackEvent, @@ -55,7 +63,9 @@ export type TrackEventName = | typeof OBJECT_RECORD_UPDATED_EVENT | typeof OBJECT_RECORD_DELETED_EVENT | typeof OBJECT_RECORD_UPSERTED_EVENT - | typeof USER_SIGNUP_EVENT; + | typeof USER_SIGNUP_EVENT + | typeof WORKSPACE_CREATED_EVENT + | typeof PAYMENT_RECEIVED_EVENT; // Map event names to their corresponding event types export interface TrackEvents { @@ -70,6 +80,8 @@ export interface TrackEvents { [OBJECT_RECORD_CREATED_EVENT]: ObjectRecordCreatedTrackEvent; [OBJECT_RECORD_UPDATED_EVENT]: ObjectRecordUpdatedTrackEvent; [OBJECT_RECORD_UPSERTED_EVENT]: ObjectRecordUpsertedTrackEvent; + [WORKSPACE_CREATED_EVENT]: WorkspaceCreatedTrackEvent; + [PAYMENT_RECEIVED_EVENT]: PaymentReceivedTrackEvent; } export type TrackEventProperties = diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/billing/payment-received.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/billing/payment-received.ts new file mode 100644 index 0000000000..31c90b390c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/billing/payment-received.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; + +export const PAYMENT_RECEIVED_EVENT = 'Payment Received' as const; +export const paymentReceivedSchema = z.strictObject({ + event: z.literal(PAYMENT_RECEIVED_EVENT), + properties: z.strictObject({ + amountPaid: z.number(), + }), +}); + +export type PaymentReceivedTrackEvent = z.infer; + +registerEvent(PAYMENT_RECEIVED_EVENT, paymentReceivedSchema); diff --git a/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created.ts b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created.ts new file mode 100644 index 0000000000..42fba30ad6 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +import { registerEvent } from 'src/engine/core-modules/audit/utils/events/workspace-event/track'; + +export const WORKSPACE_CREATED_EVENT = 'Workspace Created' as const; +export const workspaceCreatedSchema = z.strictObject({ + event: z.literal(WORKSPACE_CREATED_EVENT), + properties: z.strictObject({}), +}); + +export type WorkspaceCreatedTrackEvent = z.infer; + +registerEvent(WORKSPACE_CREATED_EVENT, workspaceCreatedSchema); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts index 6290bd5fb9..bc48cdc787 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts @@ -100,6 +100,11 @@ const createSignInUpServiceForTests = () => { { isValid: jest.fn().mockReturnValue(false), } as any, + { + createContext: jest.fn().mockReturnValue({ + insertWorkspaceEvent: jest.fn(), + }), + } as any, { createQueryRunner: jest.fn(() => queryRunnerMock), } as any, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index 5d33841516..0a6bef8389 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -9,6 +9,8 @@ import { Repository, type DataSource, type QueryRunner } from 'typeorm'; import { v4 } from 'uuid'; import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants'; +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; +import { WORKSPACE_CREATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/workspace/workspace-created'; import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { @@ -67,6 +69,7 @@ export class SignInUpService { private readonly applicationService: ApplicationService, private readonly fileCorePictureService: FileCorePictureService, private readonly enterprisePlanService: EnterprisePlanService, + private readonly auditService: AuditService, @InjectDataSource() private readonly dataSource: DataSource, ) {} @@ -594,6 +597,10 @@ export class SignInUpService { await queryRunner.commitTransaction(); + this.auditService + .createContext({ workspaceId }) + .insertWorkspaceEvent(WORKSPACE_CREATED_EVENT, {}); + return { user, workspace }; } catch (error) { if (queryRunner.isTransactionActive) { diff --git a/packages/twenty-server/src/engine/core-modules/billing-webhook/billing-webhook.module.ts b/packages/twenty-server/src/engine/core-modules/billing-webhook/billing-webhook.module.ts index 883543e0a8..1bd60cc0e7 100644 --- a/packages/twenty-server/src/engine/core-modules/billing-webhook/billing-webhook.module.ts +++ b/packages/twenty-server/src/engine/core-modules/billing-webhook/billing-webhook.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { BillingWebhookController } from 'src/engine/core-modules/billing-webhook/billing-webhook.controller'; import { BillingWebhookAlertService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-alert.service'; import { BillingWebhookCreditGrantService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-credit-grant.service'; @@ -32,6 +33,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache @Module({ imports: [ + AuditModule, FeatureFlagModule, StripeModule, MessageQueueModule, diff --git a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service.ts b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service.ts index 9e453f0cce..e5ebff1aa9 100644 --- a/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service.ts +++ b/packages/twenty-server/src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service.ts @@ -8,6 +8,8 @@ import { type Repository } from 'typeorm'; import type Stripe from 'stripe'; +import { AuditService } from 'src/engine/core-modules/audit/services/audit.service'; +import { PAYMENT_RECEIVED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/billing/payment-received'; import { getSubscriptionIdFromInvoice } from 'src/engine/core-modules/billing-webhook/utils/get-subscription-id-from-invoice.util'; import { BillingException, @@ -41,6 +43,7 @@ export class BillingWebhookInvoiceService { private readonly billingCreditRolloverService: BillingCreditRolloverService, private readonly meteredCreditService: MeteredCreditService, private readonly stripeInvoiceService: StripeInvoiceService, + private readonly auditService: AuditService, ) {} async processStripeEvent( @@ -170,7 +173,19 @@ export class BillingWebhookInvoiceService { paidInvoicePeriodEnd, ); - await this.delaySuspendedWorkspaceCleanup(stripeCustomerId); + const billingCustomer = await this.billingCustomerRepository.findOne({ + where: { stripeCustomerId }, + }); + + if (isDefined(billingCustomer)) { + await this.delaySuspendedWorkspaceCleanup(billingCustomer); + + this.auditService + .createContext({ workspaceId: billingCustomer.workspaceId }) + .insertWorkspaceEvent(PAYMENT_RECEIVED_EVENT, { + amountPaid: data.object.amount_paid, + }); + } return { stripeSubscriptionId }; } @@ -204,16 +219,8 @@ export class BillingWebhookInvoiceService { } private async delaySuspendedWorkspaceCleanup( - stripeCustomerId: string, + billingCustomer: BillingCustomerEntity, ): Promise { - const billingCustomer = await this.billingCustomerRepository.findOne({ - where: { stripeCustomerId }, - }); - - if (!isDefined(billingCustomer)) { - return; - } - const workspace = await this.workspaceRepository.findOne({ where: { id: billingCustomer.workspaceId,