22359be40e
Close billing, tenancy, migration, build, and CI/CD gaps identified in the audit: wallet/gateway guards, full-UUID namespaces, idempotent migrations with base schema, stateful service stability, safer Dockerfiles/git builds, and platform chart hardening (Redis auth, RollingUpdate, backups, Swagger off). Co-authored-by: Cursor <cursoragent@cursor.com>
231 lines
6.4 KiB
TypeScript
231 lines
6.4 KiB
TypeScript
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;
|
|
|
|
@Column({ nullable: true })
|
|
dbPassword: string;
|
|
|
|
@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<string, string>;
|
|
|
|
// 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; // <subdomain>.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<string, number>;
|
|
|
|
@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;
|
|
}
|