Allow adding or editing a storefront git repo from the Websites domain editor.

Provision uses the same websites-VM agent as Businesses; DNS still goes through Arvan ensureTenantRecords.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-31 16:32:20 +03:30
co-authored by Cursor
parent cda901d049
commit 5813e9fc53
2 changed files with 85 additions and 5 deletions
+74 -4
View File
@@ -19,6 +19,11 @@ import {
} from '../common/tls-probe';
import { PrismaService } from '../prisma/prisma.service';
import { WebsiteDeployAgentService } from '../website-deploy/website-deploy-agent.service';
import {
deploySlugFromHost,
isValidGitRepoUrl,
normalizeGitRepoUrlForClone,
} from '../website-deploy/website-deploy.util';
import { DisableDomainDto } from './dto/disable-domain.dto';
import { ListDomainsDto } from './dto/list-domains.dto';
import { ToggleSslDto } from './dto/toggle-ssl.dto';
@@ -37,6 +42,7 @@ type DomainRow = {
lastDeployedAt: Date | null;
lastDeployStatus: string | null;
deploySlug: string | null;
gitRepoUrl: string | null;
};
@Injectable()
@@ -82,7 +88,8 @@ export class DomainAdminService {
d.created_at AS "createdAt",
d.last_deployed_at AS "lastDeployedAt",
d.last_deploy_status AS "lastDeployStatus",
d.deploy_slug AS "deploySlug"
d.deploy_slug AS "deploySlug",
d.git_repo_url AS "gitRepoUrl"
FROM domains d
JOIN businesses b ON b.id = d.business_id
${where}
@@ -552,27 +559,90 @@ export class DomainAdminService {
throw new NotFoundException('Domain not found');
}
const host = (dto.host?.trim() || domain.host).toLowerCase();
const gitRepoUrl = dto.gitRepoUrl?.trim() || null;
if (dto.host) {
const host = dto.host.trim();
const existing = await this.prisma.domain.findUnique({ where: { host } });
if (existing && existing.id !== domainId) {
throw new ConflictException('Domain host is already taken');
}
}
if (gitRepoUrl && !isValidGitRepoUrl(gitRepoUrl)) {
throw new BadRequestException(
'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
);
}
let deploySlug = domain.deploySlug;
let provisionError: string | null = null;
let nextGitRepoUrl = domain.gitRepoUrl;
const hostChanged = domain.host !== host;
const effectiveGitRepoUrl = gitRepoUrl || domain.gitRepoUrl?.trim() || null;
const alreadyWired =
!!effectiveGitRepoUrl &&
!!domain.deploySlug?.trim() &&
(!gitRepoUrl || domain.gitRepoUrl?.trim() === gitRepoUrl) &&
!hostChanged;
if (effectiveGitRepoUrl && !alreadyWired) {
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: normalizeGitRepoUrlForClone(effectiveGitRepoUrl),
});
deploySlug = slug;
nextGitRepoUrl = effectiveGitRepoUrl;
} 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';
}
}
} else if (alreadyWired) {
deploySlug = domain.deploySlug;
nextGitRepoUrl = effectiveGitRepoUrl;
}
const updated = await this.prisma.domain.update({
where: { id: domainId },
data: {
host: dto.host?.trim(),
host,
expiresAt: dto.expiresAt !== undefined ? new Date(dto.expiresAt) : undefined,
...(gitRepoUrl && !provisionError
? { deploySlug, gitRepoUrl: nextGitRepoUrl }
: {}),
},
});
const host = updated.host;
// Same Arvan helper as business-admin domain add/edit (`ensureTenantRecords`).
const dnsError = dto.updateDns ? await this.applyArvanDns(host) : null;
return {
...updated,
provisionError,
dnsError,
};
}
@@ -1,4 +1,5 @@
import { IsBoolean, IsDateString, IsOptional, IsString, MinLength } from 'class-validator';
import { IsBoolean, IsDateString, IsOptional, IsString, Matches, MinLength } from 'class-validator';
import { GIT_REPO_URL_RE } from '../../website-deploy/website-deploy.util';
export class UpdateDomainAdminDto {
@IsOptional()
@@ -14,4 +15,13 @@ export class UpdateDomainAdminDto {
@IsOptional()
@IsBoolean()
updateDns?: boolean;
/** HTTPS or SSH git URL — when set, provisions storefront deploy on the websites VM. */
@IsOptional()
@IsString()
@Matches(GIT_REPO_URL_RE, {
message:
'gitRepoUrl must be a git URL (e.g. https://git.meshkee.com/Meshkee-Websites/oaktasty.git)',
})
gitRepoUrl?: string;
}