Skip to content

Commit

Permalink
Add two core tables for billing
Browse files Browse the repository at this point in the history
  • Loading branch information
martmull committed Feb 21, 2024
1 parent f260a7a commit 3a7695c
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 12 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
"scroll-into-view": "^1.16.2",
"semver": "^7.5.4",
"sharp": "^0.32.1",
"stripe": "^14.17.0",
"ts-key-enum": "^2.0.12",
"tslib": "^2.3.0",
"tsup": "^8.0.1",
Expand Down
21 changes: 21 additions & 0 deletions packages/twenty-server/src/core/billing/billing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';

import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';

import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
import { BillingProduct } from 'src/core/billing/entities/billing-product.entity';

@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature(
[BillingSubscription, BillingProduct],
'core',
),
],
}),
],
})
export class BillingModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';

import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { IDField } from '@ptc-org/nestjs-query-graphql';

import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';

@Entity({ name: 'billingProduct', schema: 'core' })
@ObjectType('BillingProduct')
export class BillingProduct {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;

@Field()
@Column({ nullable: false })
billingSubscriptionId: string;

@ManyToOne(
() => BillingSubscription,
(billingSubscription) => billingSubscription.billingProducts,
{
onDelete: 'CASCADE',
},
)
billingSubscription: BillingSubscription;

@Field()
@Column({ nullable: false })
stripeProductId: string;

@Field()
@Column({ nullable: false })
stripePriceId: string;

@Field()
@Column({ nullable: false })
quantity: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';

import {
Column,
Entity,
JoinColumn,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import Stripe from 'stripe';

import { Workspace } from 'src/core/workspace/workspace.entity';
import { BillingProduct } from 'src/core/billing/entities/billing-product.entity';

@Entity({ name: 'billingSubscription', schema: 'core' })
@ObjectType('BillingSubscription')
export class BillingSubscription {
@IDField(() => ID)
@PrimaryGeneratedColumn('uuid')
id: string;

@OneToOne(() => Workspace, (workspace) => workspace.billingSubscription, {
onDelete: 'CASCADE',
})
@JoinColumn()
workspace: Workspace;

@Field()
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;

@Field()
@Column({ unique: true, nullable: false })
stripeCustomerId: string;

@Field()
@Column({ unique: true, nullable: false })
stripeSubscriptionId: string;

@Field()
@Column({ nullable: false })
status: Stripe.Subscription.Status;

@OneToMany(
() => BillingProduct,
(billingProduct) => billingProduct.billingSubscription,
)
billingProducts: BillingProduct[];
}
22 changes: 12 additions & 10 deletions packages/twenty-server/src/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,34 @@ import { ApiRestModule } from 'src/core/api-rest/api-rest.module';
import { FeatureFlagModule } from 'src/core/feature-flag/feature-flag.module';
import { OpenApiModule } from 'src/core/open-api/open-api.module';
import { TimelineMessagingModule } from 'src/core/messaging/timeline-messaging.module';
import { BillingModule } from 'src/core/billing/billing.module';

import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
import { ClientConfigModule } from './client-config/client-config.module';

@Module({
imports: [
AuthModule,
WorkspaceModule,
UserModule,
RefreshTokenModule,
AnalyticsModule,
FileModule,
ClientConfigModule,
ApiRestModule,
OpenApiModule,
AuthModule,
BillingModule,
ClientConfigModule,
FeatureFlagModule,
FileModule,
OpenApiModule,
RefreshTokenModule,
TimelineMessagingModule,
UserModule,
WorkspaceModule,
],
exports: [
AuthModule,
WorkspaceModule,
UserModule,
AnalyticsModule,
AuthModule,
FeatureFlagModule,
TimelineMessagingModule,
UserModule,
WorkspaceModule,
],
})
export class CoreModule {}
8 changes: 8 additions & 0 deletions packages/twenty-server/src/core/workspace/workspace.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
CreateDateColumn,
Entity,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';

import { User } from 'src/core/user/user.entity';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';

@Entity({ name: 'workspace', schema: 'core' })
@ObjectType('Workspace')
Expand Down Expand Up @@ -65,4 +67,10 @@ export class Workspace {

@Field()
activationStatus: 'active' | 'inactive';

@OneToOne(
() => BillingSubscription,
(billingSubscription) => billingSubscription.workspace,
)
billingSubscription: BillingSubscription;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { WorkspaceResolver } from 'src/core/workspace/workspace.resolver';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { UserModule } from 'src/core/user/user.module';
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';

import { Workspace } from './workspace.entity';
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
Expand All @@ -21,7 +22,7 @@ import { WorkspaceService } from './services/workspace.service';
NestjsQueryGraphQLModule.forFeature({
imports: [
NestjsQueryTypeOrmModule.forFeature(
[Workspace, FeatureFlagEntity],
[Workspace, FeatureFlagEntity, BillingSubscription],
'core',
),
WorkspaceManagerModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddBillingCoreTables1708444443910 implements MigrationInterface {
name = 'AddBillingCoreTables1708444443910';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "core"."billingProduct" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "billingSubscriptionId" uuid NOT NULL, "stripeProductId" character varying NOT NULL, "stripePriceId" character varying NOT NULL, "quantity" integer NOT NULL, CONSTRAINT "PK_8bb3c7be66db8e05476808b0ca7" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "core"."billingSubscription" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "workspaceId" uuid NOT NULL, "stripeCustomerId" character varying NOT NULL, "stripeSubscriptionId" character varying NOT NULL, "status" character varying NOT NULL, CONSTRAINT "UQ_9120b7586c3471463480b58d20a" UNIQUE ("stripeCustomerId"), CONSTRAINT "UQ_1a858c28c7766d429cbd25f05e8" UNIQUE ("stripeSubscriptionId"), CONSTRAINT "REL_4abfb70314c18da69e1bee1954" UNIQUE ("workspaceId"), CONSTRAINT "PK_6e9c72c32d91640b8087cb53666" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "core"."billingProduct" ADD CONSTRAINT "FK_8362bf28155ad6651243ce00317" FOREIGN KEY ("billingSubscriptionId") REFERENCES "core"."billingSubscription"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "core"."billingSubscription" ADD CONSTRAINT "FK_4abfb70314c18da69e1bee1954d" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."billingSubscription" DROP CONSTRAINT "FK_4abfb70314c18da69e1bee1954d"`,
);
await queryRunner.query(
`ALTER TABLE "core"."billingProduct" DROP CONSTRAINT "FK_8362bf28155ad6651243ce00317"`,
);
await queryRunner.query(`DROP TABLE "core"."billingSubscription"`);
await queryRunner.query(`DROP TABLE "core"."billingProduct"`);
}
}
11 changes: 10 additions & 1 deletion packages/twenty-server/src/database/typeorm/typeorm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { User } from 'src/core/user/user.entity';
import { Workspace } from 'src/core/workspace/workspace.entity';
import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { BillingSubscription } from 'src/core/billing/entities/billing-subscription.entity';
import { BillingProduct } from 'src/core/billing/entities/billing-product.entity';

@Injectable()
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
Expand All @@ -21,7 +23,14 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
type: 'postgres',
logging: false,
schema: 'core',
entities: [User, Workspace, RefreshToken, FeatureFlagEntity],
entities: [
User,
Workspace,
RefreshToken,
FeatureFlagEntity,
BillingSubscription,
BillingProduct,
],
});
}

Expand Down
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15797,6 +15797,15 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:>=8.1.0":
version: 20.11.19
resolution: "@types/node@npm:20.11.19"
dependencies:
undici-types: "npm:~5.26.4"
checksum: f451ef0a1d78f29c57bad7b77e49ebec945f2a6d0d7a89851d7e185ee9fe7ad94d651c0dfbcb7858c9fa791310c8b40a881e2260f56bd3c1b7e7ae92723373ae
languageName: node
linkType: hard

"@types/node@npm:^10.1.0":
version: 10.17.60
resolution: "@types/node@npm:10.17.60"
Expand Down Expand Up @@ -42787,6 +42796,16 @@ __metadata:
languageName: node
linkType: hard

"stripe@npm:^14.17.0":
version: 14.17.0
resolution: "stripe@npm:14.17.0"
dependencies:
"@types/node": "npm:>=8.1.0"
qs: "npm:^6.11.0"
checksum: ec783c4b125ad6c2f8181d3aa07b7d6a7126a588310ace8d9189269014ce84ba3e98d43464bc557bfcefcc05d7c5aebc551dd4e19c32316165c76944898a719a
languageName: node
linkType: hard

"strnum@npm:^1.0.5":
version: 1.0.5
resolution: "strnum@npm:1.0.5"
Expand Down Expand Up @@ -44395,6 +44414,7 @@ __metadata:
storybook: "npm:^7.6.3"
storybook-addon-cookie: "npm:^3.2.0"
storybook-addon-pseudo-states: "npm:^2.1.2"
stripe: "npm:^14.17.0"
supertest: "npm:^6.1.3"
ts-jest: "npm:^29.1.1"
ts-key-enum: "npm:^2.0.12"
Expand Down

0 comments on commit 3a7695c

Please sign in to comment.