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
+60 -2
View File
@@ -37,6 +37,11 @@ import {
DEFAULT_ENABLED_BUSINESS_MODULES,
DEFAULT_HOME_CHARTS,
} from '../business-settings/business-settings.types';
import { WebsiteDeployAgentService } from '../website-deploy/website-deploy-agent.service';
import {
deploySlugFromHost,
isValidGitRepoUrl,
} from '../website-deploy/website-deploy.util';
type BusinessRow = {
id: bigint;
@@ -75,6 +80,7 @@ export class BusinessAdminService {
private readonly permissions: PermissionsService,
private readonly legacyMigrate: LegacyMigrateService,
private readonly legacyPurge: LegacyPurgeService,
private readonly websiteDeployAgent: WebsiteDeployAgentService,
) {}
private async assertSuperAdmin(actor: AuthUser) {
@@ -451,17 +457,29 @@ export class BusinessAdminService {
await this.assertSuperAdmin(actor);
const businessId = BigInt(businessIdRaw);
const host = dto.host.trim();
const host = dto.host.trim().toLowerCase();
const gitRepoUrl = dto.gitRepoUrl?.trim() || null;
if (!host) {
throw new BadRequestException('host is required');
}
if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) {
throw new BadRequestException(
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)',
);
}
const business = await this.prisma.business.findUnique({ where: { id: businessId } });
if (!business) {
throw new NotFoundException('Business not found');
}
const existingHost = await this.prisma.domain.findUnique({ where: { host } });
if (existingHost) {
throw new ConflictException('Domain host is already taken');
}
const hasPrimary = await this.prisma.domain.findFirst({
where: { businessId, isPrimary: true },
select: { id: true },
@@ -469,15 +487,55 @@ export class BusinessAdminService {
const isPrimary = dto.isPrimary ?? !hasPrimary;
return this.prisma.domain.create({
let deploySlug: string | null = null;
let provisionError: string | null = null;
if (gitRepoUrl) {
deploySlug = deploySlugFromHost(host);
if (!deploySlug) {
throw new BadRequestException('Could not derive deploy slug from host');
}
const slugTaken = await this.prisma.domain.findFirst({
where: { deploySlug },
select: { id: true },
});
if (slugTaken) {
throw new ConflictException(`Deploy slug "${deploySlug}" is already in use`);
}
try {
await this.websiteDeployAgent.provision({
slug: deploySlug,
host,
gitRepoUrl,
});
} catch (err) {
if (err && typeof err === 'object' && 'message' in err && typeof err.message === 'string') {
provisionError = err.message;
} else {
provisionError = 'Storefront provision failed on websites server';
}
deploySlug = null;
}
}
const domain = await this.prisma.domain.create({
data: {
businessId,
host,
isPrimary,
isVerified: false,
sslEnabled: false,
deploySlug: provisionError ? null : deploySlug,
gitRepoUrl: provisionError ? null : gitRepoUrl,
},
});
return {
...domain,
provisionError,
};
}
async updateDomain(