Files
cloudflare-domain-manager/apps/api/src/services/change-domain-service.ts
T
Denozordec 3f6f402872
quality / commitlint (push) Skipped
CD / update-wiki (push) Failing after 8s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 50s
quality / api (push) Successful in 41s
CD / quality (push) Successful in 1m40s
CD / publish (push) Successful in 1m33s
feat(health-checks): enhance health check functionality and add new routes
- Introduced origin health check routes and integrated them into the application.
- Updated health check configuration to include success recovery thresholds.
- Expanded error handling with new error codes for health check failures.
- Added new service routes for managing health checks, including creation and listing.
- Improved health check service logic to track consecutive successes and failures.

This commit enhances the health check capabilities, providing better monitoring and management of service health.
2026-08-19 12:26:12 +07:00

119 lines
3.8 KiB
TypeScript

import type { Db } from "@cfdm/db";
import { repos } from "@cfdm/db";
import type { ChangeDomainInput } from "@cfdm/shared";
import type { CloudflareClient } from "../lib/cf-client.js";
import { AppError } from "../errors.js";
import * as dnsService from "./dns-service.js";
import { withBindingLock } from "./routing/index.js";
import { applyBindingDesiredDns, fqdnToDisplay } from "./service-config-service.js";
export interface ChangeDomainItem {
binding_id: number;
hostname: string;
from_fqdn: string;
to_fqdn: string;
}
export interface ChangeDomainPreview {
from_domain_id: number;
to_domain_id: number;
from_zone: string;
to_zone: string;
items: ChangeDomainItem[];
dry_run: boolean;
applied: boolean;
message: string;
}
export async function changeServiceDomain(
db: Db,
cf: CloudflareClient,
serviceId: number,
input: ChangeDomainInput,
): Promise<ChangeDomainPreview> {
repos.getService(db, serviceId);
if (input.from_domain_id === input.to_domain_id) {
throw AppError.validation("укажите другой целевой домен");
}
const fromDomain = repos.getDomain(db, input.from_domain_id);
const toDomain = repos.getDomain(db, input.to_domain_id);
const bindings = repos
.listBindingsByService(db, serviceId)
.filter((b) => b.domain_id === input.from_domain_id);
const selected = input.hostnames?.length
? bindings.filter((b) => input.hostnames!.includes(b.hostname))
: bindings;
if (selected.length === 0) {
throw AppError.validation("нет привязок для переноса");
}
const items: ChangeDomainItem[] = selected.map((binding) => ({
binding_id: binding.id,
hostname: binding.hostname,
from_fqdn: fqdnToDisplay(binding.hostname, fromDomain.zone_name),
to_fqdn: fqdnToDisplay(binding.hostname, toDomain.zone_name),
}));
const preview: ChangeDomainPreview = {
from_domain_id: fromDomain.id,
to_domain_id: toDomain.id,
from_zone: fromDomain.zone_name,
to_zone: toDomain.zone_name,
items,
dry_run: Boolean(input.dry_run),
applied: false,
message: `Перенос ${items.length} привязок ${fromDomain.zone_name}${toDomain.zone_name}`,
};
if (input.dry_run) return preview;
const createdRecordIds: number[] = [];
try {
for (const binding of selected) {
const existing = repos.findBinding(
db,
serviceId,
toDomain.id,
binding.hostname,
);
if (existing) {
throw AppError.conflict(
`привязка ${fqdnToDisplay(binding.hostname, toDomain.zone_name)} уже существует`,
);
}
await withBindingLock(binding.id, async () => {
repos.bumpBindingVersion(db, binding.id);
const ips = repos.listBindingIps(db, binding.id);
repos.updateBindingDomain(db, binding.id, toDomain.id, binding.hostname);
await applyBindingDesiredDns(db, cf, binding.id, ips);
const newRecords = repos.listRecordsForBinding(db, binding.id);
createdRecordIds.push(...newRecords.map((r) => r.id));
const oldRecords = newRecords.filter((r) => r.domain_id === fromDomain.id);
for (const record of oldRecords) {
repos.unlinkBindingRecord(db, binding.id, record.id);
try {
await dnsService.deleteRecord(db, cf, fromDomain.id, record.id);
} catch {
// best-effort cleanup of old zone
}
}
});
}
} catch (err) {
throw err instanceof AppError
? err
: AppError.syncFailed(
err instanceof Error ? err.message : "не удалось перенести привязки",
);
}
void createdRecordIds;
return {
...preview,
dry_run: false,
applied: true,
message: `Привязки перенесены в ${toDomain.zone_name}. Старые записи зоны удалены.`,
};
}