From 20c4184cdef9faa6d44780633005c6588d80f867 Mon Sep 17 00:00:00 2001 From: keyhan Date: Tue, 26 May 2026 19:55:44 +0330 Subject: [PATCH] Generate random platform subdomains for apps without custom domains. Assign unpredictable subdomains on create when no custom domain is set, and update deploy UI CNAME hints to reference the app Platform Domain after creation. Co-authored-by: Cursor --- .../src/applications/applications.service.ts | 32 ++++++++++++++++++- frontend/src/app/dashboard/deploy/page.tsx | 20 +++--------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/backend/src/applications/applications.service.ts b/backend/src/applications/applications.service.ts index 2887b16..5e7e213 100644 --- a/backend/src/applications/applications.service.ts +++ b/backend/src/applications/applications.service.ts @@ -30,6 +30,33 @@ export class ApplicationsService { private configService: ConfigService, ) {} + private toDnsLabel(value: string): string { + return (value || '') + .toLowerCase() + .replace(/[^a-z0-9-]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') + .slice(0, 63); + } + + private async generateRandomSubdomain(baseLabel: string): Promise { + const base = this.toDnsLabel(baseLabel) || 'app'; + const suffixLenBytes = 5; // 10 hex chars + const suffixLen = suffixLenBytes * 2; + + for (let attempt = 0; attempt < 10; attempt++) { + const suffix = crypto.randomBytes(suffixLenBytes).toString('hex'); // [0-9a-f] + const maxBaseLen = 63 - 1 - suffixLen; // base + '-' + suffix + const truncatedBase = base.slice(0, Math.max(1, maxBaseLen)).replace(/-+$/g, ''); + const candidate = `${truncatedBase}-${suffix}`; + + const exists = await this.appsRepository.findOne({ where: { subdomain: candidate } }); + if (!exists) return candidate; + } + + throw new BadRequestException('Failed to generate a unique subdomain. Please try again.'); + } + async create(userId: string, dto: CreateApplicationDto, userRole?: string): Promise { dto = normalizeCreateApplicationDto(dto); const productType = dto.productType ?? ProductType.APPLICATION; @@ -83,7 +110,10 @@ export class ApplicationsService { ? 80 : 3000; - const subdomain = `${dto.name}-${userId.split('-')[0]}`; + const baseLabel = dto.name; + const subdomain = customDomain + ? `${this.toDnsLabel(baseLabel)}-${this.toDnsLabel(userId.split('-')[0])}` + : await this.generateRandomSubdomain(baseLabel); const platformDomain = this.configService.get('platform.domain') || 'apps.cloudhost.ir'; const app = this.appsRepository.create({ diff --git a/frontend/src/app/dashboard/deploy/page.tsx b/frontend/src/app/dashboard/deploy/page.tsx index 9d4bc8c..412cc1a 100644 --- a/frontend/src/app/dashboard/deploy/page.tsx +++ b/frontend/src/app/dashboard/deploy/page.tsx @@ -2086,7 +2086,7 @@ export default function DeployPage() { Type: CNAME

- Value: {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir + Value: your app Platform Domain (after creation)

4. If using a root domain (without www), use a registrar that supports CNAME flattening (e.g. Cloudflare).

@@ -2096,20 +2096,8 @@ export default function DeployPage() { {/* CNAME target copy box */}

CNAME Target:

-
- - {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir - - +
+ After the app is created, open its page and copy the “Platform Domain” value shown in the Domains/Settings section.
@@ -3055,7 +3043,7 @@ export default function DeployPage() {

Required CNAME record:

- {customDomainInput} → {form.name}-{(user?.id || '').split('-')[0]}.apps.cloudhost.ir + {customDomainInput} → your app Platform Domain (after creation)