[template-saas/backend] feat: Add 7 TypeORM entities from HU-REFACT-005
Entities created: - RefreshToken (auth.refresh_tokens) - SubscriptionItem (billing.subscription_items) - InvoiceItem (billing.invoice_items) - Payment (billing.payments) - FlagEvaluationRecord (feature_flags.evaluations) - PlanFeature (plans.plan_features) - TenantSetting (tenants.tenant_settings) Updated modules to register new entities. DDL-Entity coherence: 82.5% -> 100% Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dfe6a715f0
commit
2ffe4864dd
@ -5,7 +5,7 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
// Entities
|
||||
import { User, Session, Token, OAuthConnection } from './entities';
|
||||
import { User, Session, Token, RefreshToken, OAuthConnection } from './entities';
|
||||
|
||||
// Services
|
||||
import { AuthService } from './services/auth.service';
|
||||
@ -37,7 +37,7 @@ import { JwtStrategy } from './strategies/jwt.strategy';
|
||||
}),
|
||||
|
||||
// TypeORM entities
|
||||
TypeOrmModule.forFeature([User, Session, Token, OAuthConnection]),
|
||||
TypeOrmModule.forFeature([User, Session, Token, RefreshToken, OAuthConnection]),
|
||||
],
|
||||
controllers: [AuthController, OAuthController],
|
||||
providers: [AuthService, OAuthService, MfaService, JwtStrategy],
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './user.entity';
|
||||
export * from './session.entity';
|
||||
export * from './token.entity';
|
||||
export * from './refresh-token.entity';
|
||||
export * from './oauth-provider.enum';
|
||||
export * from './oauth-connection.entity';
|
||||
|
||||
56
src/modules/auth/entities/refresh-token.entity.ts
Normal file
56
src/modules/auth/entities/refresh-token.entity.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* RefreshToken Entity
|
||||
* Maps to auth.refresh_tokens DDL table
|
||||
* Supports JWT refresh token rotation with family tracking
|
||||
*/
|
||||
@Entity({ schema: 'auth', name: 'refresh_tokens' })
|
||||
export class RefreshToken {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
tenant_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
user_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
@Index()
|
||||
session_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
@Index({ unique: true })
|
||||
token_hash: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
family_id: string;
|
||||
|
||||
@Column({ type: 'int', default: 1 })
|
||||
generation: number;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_revoked: boolean;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||
revoked_at: Date | null;
|
||||
|
||||
@Column({ type: 'timestamp with time zone' })
|
||||
expires_at: Date;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||
used_at: Date | null;
|
||||
}
|
||||
@ -4,12 +4,30 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { BillingController } from './billing.controller';
|
||||
import { BillingService, StripeService, PlansService } from './services';
|
||||
import { StripeController, StripeWebhookController, PlansController } from './controllers';
|
||||
import { Subscription, Invoice, PaymentMethod, Plan } from './entities';
|
||||
import {
|
||||
Subscription,
|
||||
SubscriptionItem,
|
||||
Invoice,
|
||||
InvoiceItem,
|
||||
Payment,
|
||||
PaymentMethod,
|
||||
Plan,
|
||||
PlanFeature,
|
||||
} from './entities';
|
||||
import { RbacModule } from '../rbac/rbac.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Subscription, Invoice, PaymentMethod, Plan]),
|
||||
TypeOrmModule.forFeature([
|
||||
Subscription,
|
||||
SubscriptionItem,
|
||||
Invoice,
|
||||
InvoiceItem,
|
||||
Payment,
|
||||
PaymentMethod,
|
||||
Plan,
|
||||
PlanFeature,
|
||||
]),
|
||||
ConfigModule,
|
||||
RbacModule,
|
||||
],
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
export * from './subscription.entity';
|
||||
export * from './subscription-item.entity';
|
||||
export * from './invoice.entity';
|
||||
export * from './invoice-item.entity';
|
||||
export * from './payment.entity';
|
||||
export * from './payment-method.entity';
|
||||
export * from './plan.entity';
|
||||
export * from './plan-feature.entity';
|
||||
|
||||
52
src/modules/billing/entities/invoice-item.entity.ts
Normal file
52
src/modules/billing/entities/invoice-item.entity.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* InvoiceItem Entity
|
||||
* Maps to billing.invoice_items DDL table
|
||||
* Line items within an invoice
|
||||
*/
|
||||
@Entity({ schema: 'billing', name: 'invoice_items' })
|
||||
export class InvoiceItem {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
invoice_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 500 })
|
||||
description: string;
|
||||
|
||||
@Column({ type: 'int', default: 1 })
|
||||
quantity: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
unit_amount: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||
currency: string;
|
||||
|
||||
@Column({ type: 'date', nullable: true })
|
||||
period_start: Date | null;
|
||||
|
||||
@Column({ type: 'date', nullable: true })
|
||||
period_end: Date | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
stripe_invoice_item_id: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
metadata: Record<string, unknown>;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
}
|
||||
79
src/modules/billing/entities/payment.entity.ts
Normal file
79
src/modules/billing/entities/payment.entity.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Payment Entity
|
||||
* Maps to billing.payments DDL table
|
||||
* Payment records and attempts
|
||||
*/
|
||||
@Entity({ schema: 'billing', name: 'payments' })
|
||||
export class Payment {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
tenant_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
@Index()
|
||||
invoice_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
@Index({ unique: true })
|
||||
stripe_payment_intent_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
stripe_charge_id: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||
currency: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, default: 'pending' })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
payment_method_type: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 4, nullable: true })
|
||||
payment_method_last4: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
payment_method_brand: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
failure_code: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
failure_message: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updated_at: Date;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||
succeeded_at: Date | null;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||
failed_at: Date | null;
|
||||
|
||||
@Column({ type: 'timestamp with time zone', nullable: true })
|
||||
refunded_at: Date | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
refund_amount: number | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 500, nullable: true })
|
||||
refund_reason: string | null;
|
||||
}
|
||||
46
src/modules/billing/entities/plan-feature.entity.ts
Normal file
46
src/modules/billing/entities/plan-feature.entity.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* PlanFeature Entity
|
||||
* Maps to plans.plan_features DDL table
|
||||
* Detailed feature matrix per plan
|
||||
*/
|
||||
@Entity({ schema: 'plans', name: 'plan_features' })
|
||||
export class PlanFeature {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
plan_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
feature_code: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
feature_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
category: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
value_type: string;
|
||||
|
||||
@Column({ type: 'boolean', nullable: true })
|
||||
value_boolean: boolean | null;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
value_number: number | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 200, nullable: true })
|
||||
value_text: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
display_value: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_highlighted: boolean;
|
||||
|
||||
@Column({ type: 'int', default: 0 })
|
||||
sort_order: number;
|
||||
}
|
||||
43
src/modules/billing/entities/subscription-item.entity.ts
Normal file
43
src/modules/billing/entities/subscription-item.entity.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* SubscriptionItem Entity
|
||||
* Maps to billing.subscription_items DDL table
|
||||
* Line items within a subscription (metered/usage-based billing)
|
||||
*/
|
||||
@Entity({ schema: 'billing', name: 'subscription_items' })
|
||||
export class SubscriptionItem {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
subscription_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
stripe_subscription_item_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
stripe_price_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 200, nullable: true })
|
||||
product_name: string | null;
|
||||
|
||||
@Column({ type: 'int', default: 1 })
|
||||
quantity: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
unit_amount: number | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_metered: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
}
|
||||
44
src/modules/feature-flags/entities/flag-evaluation.entity.ts
Normal file
44
src/modules/feature-flags/entities/flag-evaluation.entity.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* FlagEvaluationRecord Entity
|
||||
* Maps to feature_flags.evaluations DDL table
|
||||
* Flag evaluation history for analytics
|
||||
*/
|
||||
@Entity({ schema: 'feature_flags', name: 'evaluations' })
|
||||
export class FlagEvaluationRecord {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
tenant_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
flag_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
user_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
flag_code: string;
|
||||
|
||||
@Column({ type: 'boolean' })
|
||||
result: boolean;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
evaluation_reason: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
context: Record<string, unknown>;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './feature-flag.entity';
|
||||
export * from './tenant-flag.entity';
|
||||
export * from './user-flag.entity';
|
||||
export * from './flag-evaluation.entity';
|
||||
|
||||
@ -2,11 +2,11 @@ import { Module, Global } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { FeatureFlagsController } from './feature-flags.controller';
|
||||
import { FeatureFlagsService } from './services/feature-flags.service';
|
||||
import { FeatureFlag, TenantFlag, UserFlag } from './entities';
|
||||
import { FeatureFlag, TenantFlag, UserFlag, FlagEvaluationRecord } from './entities';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([FeatureFlag, TenantFlag, UserFlag])],
|
||||
imports: [TypeOrmModule.forFeature([FeatureFlag, TenantFlag, UserFlag, FlagEvaluationRecord])],
|
||||
controllers: [FeatureFlagsController],
|
||||
providers: [FeatureFlagsService],
|
||||
exports: [FeatureFlagsService],
|
||||
|
||||
2
src/modules/tenants/entities/index.ts
Normal file
2
src/modules/tenants/entities/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './tenant.entity';
|
||||
export * from './tenant-setting.entity';
|
||||
56
src/modules/tenants/entities/tenant-setting.entity.ts
Normal file
56
src/modules/tenants/entities/tenant-setting.entity.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* TenantSetting Entity
|
||||
* Maps to tenants.tenant_settings DDL table
|
||||
* Structured tenant settings for queryable configuration
|
||||
*/
|
||||
@Entity({ schema: 'tenants', name: 'tenant_settings' })
|
||||
export class TenantSetting {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Index()
|
||||
tenant_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
category: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
key: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 1000, nullable: true })
|
||||
value_string: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 20, scale: 4, nullable: true })
|
||||
value_number: number | null;
|
||||
|
||||
@Column({ type: 'boolean', nullable: true })
|
||||
value_boolean: boolean | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
value_json: Record<string, unknown> | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_sensitive: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updated_at: Date;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
updated_by: string | null;
|
||||
}
|
||||
@ -2,11 +2,11 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { TenantsController } from './tenants.controller';
|
||||
import { TenantsService } from './tenants.service';
|
||||
import { Tenant } from './entities/tenant.entity';
|
||||
import { Tenant, TenantSetting } from './entities';
|
||||
import { RbacModule } from '../rbac/rbac.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Tenant]), RbacModule],
|
||||
imports: [TypeOrmModule.forFeature([Tenant, TenantSetting]), RbacModule],
|
||||
controllers: [TenantsController],
|
||||
providers: [TenantsService],
|
||||
exports: [TenantsService],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user