Enable storefront provision from Add Domain via optional git URL.

Persist deploy_slug on domains, call the websites agent /provision endpoint, and drop the hard-coded host map so Deploy appears from the UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 14:07:56 +03:30
co-authored by Cursor
parent 9ca6e2306f
commit d933aef40b
15 changed files with 604 additions and 46 deletions
+2 -1
View File
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from '../auth/auth.module';
import { WebsiteDeployModule } from '../website-deploy/website-deploy.module';
import { DomainAdminController } from './domain-admin.controller';
import { DomainAdminService } from './domain-admin.service';
@Module({
imports: [AuthModule, ConfigModule],
imports: [AuthModule, ConfigModule, WebsiteDeployModule],
controllers: [DomainAdminController],
providers: [DomainAdminService],
})
+13 -41
View File
@@ -11,17 +11,12 @@ import { Prisma } from '@prisma/client';
import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service';
import { PrismaService } from '../prisma/prisma.service';
import { WebsiteDeployAgentService } from '../website-deploy/website-deploy-agent.service';
import { DisableDomainDto } from './dto/disable-domain.dto';
import { ListDomainsDto } from './dto/list-domains.dto';
import { ToggleSslDto } from './dto/toggle-ssl.dto';
import { UpdateDomainAdminDto } from './dto/update-domain-admin.dto';
/** Apex hosts that have a storefront deploy on the websites VM. */
const WEBSITE_DEPLOY_SLUGS: Record<string, string> = {
'ali-mohammadi.ir': 'ali-mohammadi',
'meshkee.com': 'meshkee',
};
type DomainRow = {
id: bigint;
host: string;
@@ -33,6 +28,7 @@ type DomainRow = {
createdAt: Date;
lastDeployedAt: Date | null;
lastDeployStatus: string | null;
deploySlug: string | null;
};
@Injectable()
@@ -41,6 +37,7 @@ export class DomainAdminService {
private readonly prisma: PrismaService,
private readonly permissions: PermissionsService,
private readonly config: ConfigService,
private readonly websiteDeployAgent: WebsiteDeployAgentService,
) {}
private async assertSuperAdmin(actor: AuthUser) {
@@ -49,10 +46,6 @@ export class DomainAdminService {
}
}
private deploySlugForHost(host: string): string | null {
return WEBSITE_DEPLOY_SLUGS[host.trim().toLowerCase()] ?? null;
}
async list(query: ListDomainsDto, actor: AuthUser) {
await this.assertSuperAdmin(actor);
@@ -78,7 +71,8 @@ export class DomainAdminService {
d.expires_at AS "expiresAt",
d.created_at AS "createdAt",
d.last_deployed_at AS "lastDeployedAt",
d.last_deploy_status AS "lastDeployStatus"
d.last_deploy_status AS "lastDeployStatus",
d.deploy_slug AS "deploySlug"
FROM domains d
JOIN businesses b ON b.id = d.business_id
${where}
@@ -92,12 +86,7 @@ export class DomainAdminService {
`),
]);
const items = rows.map((row) => ({
...row,
deploySlug: this.deploySlugForHost(row.host),
}));
return { items, total: totalRow[0]?.total ?? 0, page, pageSize };
return { items: rows, total: totalRow[0]?.total ?? 0, page, pageSize };
}
async syncSsl(actor: AuthUser) {
@@ -149,17 +138,11 @@ export class DomainAdminService {
throw new NotFoundException('Domain not found');
}
const slug = this.deploySlugForHost(domain.host);
const slug = domain.deploySlug?.trim() || null;
if (!slug) {
throw new BadRequestException('This domain has no storefront deploy configured');
}
const agentUrl = this.config.get<string>('WEBSITE_DEPLOY_AGENT_URL')?.trim();
const token = this.config.get<string>('WEBSITE_DEPLOY_TOKEN')?.trim();
if (!agentUrl || !token) {
throw new ServiceUnavailableException('Website deploy agent is not configured');
}
const markDeploy = async (status: 'started' | 'failed') => {
const updated = await this.prisma.domain.update({
where: { id: domainId },
@@ -171,26 +154,15 @@ export class DomainAdminService {
return updated;
};
let response: Response;
try {
response = await fetch(agentUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Deploy-Token': token,
},
body: JSON.stringify({ slug }),
});
} catch {
await this.websiteDeployAgent.deploy(slug);
} catch (err) {
await markDeploy('failed');
throw new ServiceUnavailableException('Could not reach website deploy agent');
}
if (!response.ok) {
await markDeploy('failed');
const text = await response.text().catch(() => '');
if (err instanceof ServiceUnavailableException) {
throw err;
}
throw new ServiceUnavailableException(
`Deploy agent rejected request (${response.status})${text ? `: ${text}` : ''}`,
err instanceof Error ? err.message : 'Deploy failed to start',
);
}