import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany, JoinColumn, } from 'typeorm'; import { AppRuntime, DatabaseType, BillingCycle, AppLifecycleStatus, CustomDomainStatus, ProductType, } from '../../common/enums'; import { Exclude, Expose } from 'class-transformer'; import { User } from '../../users/entities/user.entity'; import { Deployment } from '../../deployments/entities/deployment.entity'; @Entity('applications') export class Application { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: string; @Column({ type: 'varchar', default: ProductType.APPLICATION }) productType: ProductType; @Column({ nullable: true }) description: string; @Column({ type: 'enum', enum: AppRuntime }) runtime: AppRuntime; @Column({ type: 'enum', enum: DatabaseType, default: DatabaseType.NONE }) databaseType: DatabaseType; @Column({ nullable: true }) runtimeVersion: string; // node: '20','18','16' | laravel/wordpress php: '8.3','8.2' | go: '1.22','1.21' | python/django: '3.12','3.11' | dotnet: '8.0','7.0' @Column({ nullable: true }) phpVersion: string; // PHP version for Laravel/WordPress/PHP (e.g. '8.3', '8.2', '8.1') @Column({ nullable: true }) dbVersion: string; // e.g. postgres: '17', '16', '15' | mysql: '9.0', '8.4', '8.0' @Column({ nullable: true }) dbUsername: string; /** Never expose raw DB password in API responses — use hasDbPassword for UI. */ @Exclude({ toPlainOnly: true }) @Column({ nullable: true }) dbPassword: string; @Expose() get hasDbPassword(): boolean { return !!this.dbPassword; } @Column({ nullable: true, default: '1Gi' }) dbStorageSize: string; // PVC storage size for database (e.g. '1Gi', '5Gi', '10Gi') @Column({ nullable: true, default: '2Gi' }) appStorageSize: string; // PVC storage size for app files (wp-content, uploads) // ── Optional Services ───────────────────────────── @Column({ default: false }) enableRedis: boolean; @Column({ nullable: true, default: '7.2' }) redisVersion: string; @Column({ default: false }) enableRabbitmq: boolean; @Column({ nullable: true, default: '3.13' }) rabbitmqVersion: string; @Column({ default: false }) enableElasticsearch: boolean; // For application logging @Column({ nullable: true, default: '8.12' }) elasticsearchVersion: string; @Column({ type: 'jsonb', nullable: true }) logPaths: string[]; // Custom log paths to collect (e.g., ['/app/logs/*.log']) /** User-selected CPU/RAM/storage per optional service (database, redis, rabbitmq). */ @Column({ type: 'jsonb', nullable: true }) optionalServiceResources?: { database?: { cpuRequest?: string; cpuLimit: string; memoryRequest?: string; memoryLimit: string; storageGi: number; }; redis?: { cpuRequest?: string; cpuLimit: string; memoryRequest?: string; memoryLimit: string; storageGi: number; }; rabbitmq?: { cpuRequest?: string; cpuLimit: string; memoryRequest?: string; memoryLimit: string; storageGi: number; }; }; @Column({ nullable: true }) gitUrl: string; /** * Personal access token for private repos. Never serialized into API * responses (see hasGitToken) — it is a credential to an external system. */ @Exclude({ toPlainOnly: true }) @Column({ nullable: true }) gitToken: string; /** Whether a git token is configured (safe indicator for the UI). */ @Expose() get hasGitToken(): boolean { return !!this.gitToken; } @Column({ nullable: true }) gitBranch: string; // Branch to clone (default: main) @Column({ nullable: true }) codePath: string; // Path to uploaded zip @Column({ nullable: true }) dbDumpPath: string; // Path to uploaded SQL dump file @Column({ type: 'jsonb', nullable: true }) envVars: Record; // Resource configuration @Column({ default: '100m' }) cpuRequest: string; @Column({ default: '500m' }) cpuLimit: string; @Column({ default: '128Mi' }) memoryRequest: string; @Column({ default: '512Mi' }) memoryLimit: string; @Column({ default: 1 }) replicas: number; @Column({ default: 3000 }) port: number; // Relations @ManyToOne(() => User, (user: User) => user.applications, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'userId' }) user: User; @Column() userId: string; @Column({ type: 'uuid', nullable: true }) clusterId: string; @Column({ type: 'uuid', nullable: true }) poolId: string; // Cluster pool used for load-balanced assignment @OneToMany(() => Deployment, (deployment: Deployment) => deployment.application) deployments: Deployment[]; // Metadata @Column({ nullable: true }) latestImageTag: string; @Column({ nullable: true }) subdomain: string; // .apps.cloudhost.local // ── Custom Domain ───────────────────────────── @Column({ nullable: true }) customDomain?: string; // e.g. "www.example.com" @Column({ type: 'enum', enum: CustomDomainStatus, default: CustomDomainStatus.NONE }) customDomainStatus: CustomDomainStatus; @Column({ type: 'timestamptz', nullable: true }) customDomainVerifiedAt?: Date; // ── Billing & Lifecycle ───────────────────────────── @Column({ nullable: true }) planId: string; // FK to ServicePlan @Column({ type: 'enum', enum: BillingCycle, nullable: true }) billingCycle: BillingCycle; // The cycle this app was purchased under @Column({ type: 'enum', enum: AppLifecycleStatus, default: AppLifecycleStatus.ACTIVE }) lifecycleStatus: AppLifecycleStatus; @Column({ type: 'timestamptz', nullable: true }) planExpiresAt: Date; // When the current billing period ends @Column({ type: 'timestamptz', nullable: true }) suspendedAt: Date; // When the app was suspended (pods scaled to 0) /** Per-deployment replica counts captured before suspend (deployment name → replicas). */ @Column({ type: 'jsonb', nullable: true }) suspendedReplicas?: Record; @Column({ type: 'timestamptz', nullable: true }) scheduledDeletionAt: Date; // When the app will be permanently deleted /** When the user voluntarily removed the app (data docked for restore). */ @Column({ type: 'timestamptz', nullable: true }) dockedAt?: Date; /** Snapshot captured at dock time — used to restore DB / wp-content / source. */ @Column({ nullable: true }) dockSnapshotId?: string; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; }