Allow storefront provision when editing a domain git URL.

Edit Domain can wire deploy_slug the same way as Add Domain so existing businesses get a Deploy button without recreating the domain.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-08 14:18:31 +03:30
co-authored by Cursor
parent d933aef40b
commit 7d123502f8
2 changed files with 70 additions and 5 deletions
+60 -4
View File
@@ -55,6 +55,8 @@ type BusinessRow = {
domainId: bigint | null;
domain: string | null;
sslEnabled: boolean | null;
gitRepoUrl: string | null;
deploySlug: string | null;
ownerUserId: bigint | null;
ownerName: string | null;
ownerNameEn: string | null;
@@ -134,6 +136,8 @@ export class BusinessAdminService {
dom.id AS "domainId",
dom.host AS "domain",
dom.ssl_enabled AS "sslEnabled",
dom.git_repo_url AS "gitRepoUrl",
dom.deploy_slug AS "deploySlug",
own."ownerUserId" AS "ownerUserId",
own."ownerName" AS "ownerName",
own."ownerNameEn" AS "ownerNameEn",
@@ -144,7 +148,7 @@ export class BusinessAdminService {
b.settings->'modules'->'charts' AS "homeCharts"
FROM businesses b
LEFT JOIN LATERAL (
SELECT d.id, d.host, d.ssl_enabled
SELECT d.id, d.host, d.ssl_enabled, d.git_repo_url, d.deploy_slug
FROM domains d
WHERE d.business_id = b.id
ORDER BY d.is_primary DESC, d.created_at DESC
@@ -548,12 +552,19 @@ export class BusinessAdminService {
const businessId = BigInt(businessIdRaw);
const domainId = BigInt(domainIdRaw);
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 domain = await this.prisma.domain.findFirst({
where: { id: domainId, businessId },
});
@@ -567,10 +578,55 @@ export class BusinessAdminService {
throw new ConflictException('Domain host is already taken');
}
return this.prisma.domain.update({
let deploySlug = domain.deploySlug;
let provisionError: string | null = null;
let nextGitRepoUrl = domain.gitRepoUrl;
if (gitRepoUrl) {
const slug = domain.deploySlug?.trim() || deploySlugFromHost(host);
if (!slug) {
throw new BadRequestException('Could not derive deploy slug from host');
}
const slugTaken = await this.prisma.domain.findFirst({
where: { deploySlug: slug, NOT: { id: domainId } },
select: { id: true },
});
if (slugTaken) {
throw new ConflictException(`Deploy slug "${slug}" is already in use`);
}
try {
await this.websiteDeployAgent.provision({
slug,
host,
gitRepoUrl,
});
deploySlug = slug;
nextGitRepoUrl = 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';
}
}
}
const updated = await this.prisma.domain.update({
where: { id: domainId },
data: { host },
data: {
host,
...(gitRepoUrl && !provisionError
? { deploySlug, gitRepoUrl: nextGitRepoUrl }
: {}),
},
});
return {
...updated,
provisionError,
};
}
async disable(businessIdRaw: string, dto: DisableBusinessDto, actor: AuthUser) {
+10 -1
View File
@@ -1,7 +1,16 @@
import { IsString, MinLength } from 'class-validator';
import { IsOptional, IsString, Matches, MinLength } from 'class-validator';
export class UpdateDomainDto {
@IsString()
@MinLength(1)
host!: string;
/** SSH git URL — when set, provisions storefront deploy on the websites VM. */
@IsOptional()
@IsString()
@Matches(/^(?:git@[\w.-]+:[\w./-]+\.git|ssh:\/\/git@[\w.-]+(?::\d+)?\/[\w./-]+\.git)$/i, {
message:
'gitRepoUrl must be an SSH git URL (e.g. git@git.meshkee.com:Meshkee-Websites/oaktasty.git)',
})
gitRepoUrl?: string;
}