feat: add custom domain support with SSL, DNS verification, and billing

Users can assign a custom domain to their app with automatic SSL via
cert-manager. Includes DNS verification flow (CNAME check), Persian
instructions, admin-configurable pricing via PlatformSetting, and
integration into the deploy wizard cost calculation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-14 00:36:29 +03:30
parent d87b50c6a4
commit 435cf92817
18 changed files with 1082 additions and 89 deletions
@@ -8,7 +8,7 @@ import {
OneToMany,
JoinColumn,
} from 'typeorm';
import { AppRuntime, DatabaseType, BillingCycle, AppLifecycleStatus } from '../../common/enums';
import { AppRuntime, DatabaseType, BillingCycle, AppLifecycleStatus, CustomDomainStatus } from '../../common/enums';
import { User } from '../../users/entities/user.entity';
import { Deployment } from '../../deployments/entities/deployment.entity';
@@ -54,12 +54,21 @@ export class Application {
@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'])
@@ -124,6 +133,16 @@ export class Application {
@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