Allow add/edit domain to upsert tenant DNS on Cloudflare or Arvan.

Update DNS writes @, www, business, customer, and api records on the chosen provider instead of Arvan only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-31 18:46:48 +03:30
co-authored by Cursor
parent d1f60b5313
commit f42e6fe2a4
8 changed files with 101 additions and 35 deletions
+42 -17
View File
@@ -28,6 +28,37 @@ export class CloudflareDnsService {
created: string[];
updated: string[];
skipped: string[];
}> {
return this.ensureCnameRecords(hostRaw, (host) => [
{ relative: '@', content: TARGET },
{ relative: 'www', content: host },
]);
}
/**
* Full Meshkee tenant: @ + www + business + customer + api, DNS-only.
*/
async ensureTenantRecords(hostRaw: string): Promise<{
created: string[];
updated: string[];
skipped: string[];
}> {
return this.ensureCnameRecords(hostRaw, (host) => [
{ relative: '@', content: TARGET },
{ relative: 'www', content: host },
{ relative: 'business', content: 'business.meshkee.com' },
{ relative: 'customer', content: 'customer.meshkee.com' },
{ relative: 'api', content: 'api.meshkee.com' },
]);
}
private async ensureCnameRecords(
hostRaw: string,
desiredFor: (apex: string) => Array<{ relative: string; content: string }>,
): Promise<{
created: string[];
updated: string[];
skipped: string[];
}> {
const host = hostRaw.trim().toLowerCase();
if (!host) {
@@ -47,15 +78,10 @@ export class CloudflareDnsService {
const updated: string[] = [];
const skipped: string[] = [];
const desired: Array<{ type: 'CNAME'; relative: string; content: string }> = [
{ type: 'CNAME', relative: '@', content: TARGET },
{ type: 'CNAME', relative: 'www', content: host },
];
for (const item of desired) {
const label = `${item.type} ${item.relative}`;
for (const item of desiredFor(host)) {
const label = `CNAME ${item.relative}`;
const match = existing.find(
(rec) => rec.type === item.type && this.relativeName(rec.name, host) === item.relative,
(rec) => rec.type === 'CNAME' && this.relativeName(rec.name, host) === item.relative,
);
if (match && this.sameCname(match, item.content) && match.proxied === false) {
@@ -63,10 +89,7 @@ export class CloudflareDnsService {
continue;
}
// Apex CNAME cannot coexist with A/AAAA.
if (item.relative === '@') {
await this.deleteConflictingApexAddress(zoneId, token, existing, host);
}
await this.deleteConflictingAddress(zoneId, token, existing, host, item.relative);
if (match) {
const patch = await this.request(
@@ -74,7 +97,7 @@ export class CloudflareDnsService {
`/zones/${zoneId}/dns_records/${match.id}`,
token,
{
type: item.type,
type: 'CNAME',
name: item.relative,
content: item.content,
ttl: TTL,
@@ -91,7 +114,7 @@ export class CloudflareDnsService {
}
const post = await this.request('POST', `/zones/${zoneId}/dns_records`, token, {
type: item.type,
type: 'CNAME',
name: item.relative,
content: item.content,
ttl: TTL,
@@ -163,21 +186,23 @@ export class CloudflareDnsService {
return items;
}
private async deleteConflictingApexAddress(
private async deleteConflictingAddress(
zoneId: string,
token: string,
existing: CfRecord[],
zone: string,
relative: string,
) {
const conflicts = existing.filter(
(rec) =>
(rec.type === 'A' || rec.type === 'AAAA') && this.relativeName(rec.name, zone) === '@',
(rec.type === 'A' || rec.type === 'AAAA') &&
this.relativeName(rec.name, zone) === relative,
);
for (const rec of conflicts) {
const del = await this.request('DELETE', `/zones/${zoneId}/dns_records/${rec.id}`, token);
if (!del.ok) {
throw new ServiceUnavailableException(
`Cloudflare failed to replace ${rec.type} @ (${del.status})${del.message ? `: ${del.message}` : ''}`,
`Cloudflare failed to replace ${rec.type} ${relative} (${del.status})${del.message ? `: ${del.message}` : ''}`,
);
}
}