feat:Update pnpm-lock.yaml to link shared package; modify API routes to utilize new schemas for domain and service management; enhance DNS record handling with CNAME support; refactor service and subdomain routes for improved functionality; implement confirm dialog for domain deletion in the frontend; clean up unused components and improve UI consistency.
Build, Test, and Push CFDM Docker Image / test (push) Failing after 51s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

This commit is contained in:
Denozordec
2026-06-22 13:52:33 +07:00
parent 4a70cf5854
commit 6d1d8ca4f3
99 changed files with 6692 additions and 1483 deletions
+389 -87
View File
@@ -424,6 +424,7 @@ async function groupRoutes(app2) {
// src/routes/services.ts
import { z as z3 } from "zod";
import { reorderServicesSchema } from "@cfdm/shared";
import { repos as repos7 } from "@cfdm/db";
// src/services/service-config-service.ts
@@ -431,7 +432,8 @@ import { repos as repos6 } from "@cfdm/db";
import {
SYNC_ERROR as SYNC_ERROR2,
SYNC_PENDING_PUSH as SYNC_PENDING_PUSH3,
SYNC_SYNCED as SYNC_SYNCED3
SYNC_SYNCED as SYNC_SYNCED3,
dnsNameToSubdomainLabel as dnsNameToSubdomainLabel2
} from "@cfdm/shared";
// src/lib/validators.ts
@@ -874,9 +876,9 @@ async function createDomain(db, cf, groupId, zoneName) {
"\u043D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u0437\u043E\u043D \u0432 Cloudflare \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 CLOUDFLARE_API_TOKEN \u0438 \u043F\u0440\u0430\u0432\u0430 Zone:Read"
);
}
const zone = zones.find((z9) => z9.name.toLowerCase() === trimmed.toLowerCase());
const zone = zones.find((z8) => z8.name.toLowerCase() === trimmed.toLowerCase());
if (!zone) {
const names = zones.map((z9) => z9.name).join(", ");
const names = zones.map((z8) => z8.name).join(", ");
throw AppError.notFound(
`\u0437\u043E\u043D\u0430 \xAB${trimmed}\xBB \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 Cloudflare. \u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435: ${names}`
);
@@ -956,13 +958,19 @@ async function buildView(db, serviceId) {
const records = repos6.listRecordsForBinding(db, binding.id);
const statuses = records.map((r) => r.sync_status);
const targetIps = repos6.listBindingIps(db, binding.id);
const linkedCname = records.find(
(record) => record.record_type.toUpperCase() === "CNAME"
);
const targetCname = binding.cname_target?.trim() || linkedCname?.content?.trim() || null;
return {
binding_id: binding.id,
domain_id: binding.domain_id,
zone_name: binding.zone_name,
hostname: binding.hostname,
fqdn: fqdnToDisplay(binding.hostname, binding.zone_name),
target_ips: targetIps,
record_type: targetCname ? "CNAME" : "A",
target_ips: targetCname ? [] : targetIps,
target_cname: targetCname,
sync_status: aggregateSyncStatus(statuses)
};
});
@@ -1012,14 +1020,114 @@ function shouldPushDns(db, service) {
const group = repos6.getServiceGroup(db, service.service_group_id);
return group.enabled;
}
async function syncBindingDns(db, cf, bindingId, domainId, hostname, desiredIps) {
async function syncBindingDns(db, cf, bindingId, domainId, hostname, desiredIps, cnameTarget) {
const domain = repos6.getDomain(db, domainId);
const zoneName = domain.zone_name;
let effectiveCname = cnameTarget?.trim() || null;
if (!effectiveCname) {
const existingCname = await findOrImportDnsRecord(
db,
cf,
domainId,
zoneName,
hostname,
"CNAME"
);
if (existingCname) {
effectiveCname = existingCname.content;
repos6.setBindingCnameTarget(db, bindingId, effectiveCname);
repos6.replaceBindingIps(db, bindingId, []);
}
}
if (effectiveCname) {
await syncBindingCnameDns(
db,
cf,
bindingId,
domainId,
hostname,
effectiveCname
);
return;
}
await syncBindingADns(db, cf, bindingId, domainId, hostname, desiredIps);
}
async function syncBindingCnameDns(db, cf, bindingId, domainId, hostname, cnameTarget) {
const domain = repos6.getDomain(db, domainId);
const zoneName = domain.zone_name;
const normalized = normalizeCnameTarget(cnameTarget, zoneName);
const existingRecords = repos6.listRecordsForBinding(db, bindingId);
for (const record of existingRecords) {
if (!desiredIps.includes(record.content)) {
if (record.record_type.toUpperCase() === "A") {
repos6.unlinkBindingRecord(db, bindingId, record.id);
await deleteRecord(db, cf, domainId, record.id);
}
}
const refreshed = repos6.listRecordsForBinding(db, bindingId);
const existingCname = refreshed.find(
(record) => record.record_type.toUpperCase() === "CNAME"
);
let recordId;
if (existingCname) {
if (!cnameContentMatches(existingCname.content, normalized, zoneName) || existingCname.name !== hostname) {
await update(db, cf, domainId, existingCname.id, {
record_type: "CNAME",
name: hostname,
content: normalized,
proxied: false
});
}
recordId = existingCname.id;
} else {
const adopted = await findOrImportDnsRecord(
db,
cf,
domainId,
zoneName,
hostname,
"CNAME",
normalized
);
if (adopted) {
repos6.linkBindingRecord(db, bindingId, adopted.id);
if (!cnameContentMatches(adopted.content, normalized, zoneName)) {
await update(db, cf, domainId, adopted.id, {
record_type: "CNAME",
name: hostname,
content: normalized,
proxied: false
});
}
recordId = adopted.id;
} else {
const record = await create(db, cf, domainId, {
record_type: "CNAME",
name: hostname,
content: normalized,
ttl: 1,
proxied: false
});
repos6.linkBindingRecord(db, bindingId, record.id);
recordId = record.id;
}
}
repos6.setBindingDnsRecordId(db, bindingId, recordId);
repos6.setBindingCnameTarget(db, bindingId, normalized);
}
async function syncBindingADns(db, cf, bindingId, domainId, hostname, desiredIps) {
const domain = repos6.getDomain(db, domainId);
const zoneName = domain.zone_name;
const existingRecords = repos6.listRecordsForBinding(db, bindingId);
for (const record of existingRecords) {
if (record.record_type.toUpperCase() === "CNAME") {
repos6.unlinkBindingRecord(db, bindingId, record.id);
await deleteRecord(db, cf, domainId, record.id);
} else if (!desiredIps.includes(record.content)) {
repos6.unlinkBindingRecord(db, bindingId, record.id);
await deleteRecord(db, cf, domainId, record.id);
}
}
repos6.setBindingCnameTarget(db, bindingId, null);
if (desiredIps.length === 0) {
repos6.setBindingDnsRecordId(db, bindingId, null);
return;
@@ -1040,22 +1148,36 @@ async function syncBindingDns(db, cf, bindingId, domainId, hostname, desiredIps)
}
recordId = existing.id;
} else {
const record = await create(db, cf, domainId, {
record_type: "A",
name: hostname,
content: ip,
ttl: 1,
proxied: false
});
repos6.linkBindingRecord(db, bindingId, record.id);
recordId = record.id;
const adopted = await findOrImportDnsRecord(
db,
cf,
domainId,
zoneName,
hostname,
"A",
ip
);
if (adopted) {
repos6.linkBindingRecord(db, bindingId, adopted.id);
recordId = adopted.id;
} else {
const record = await create(db, cf, domainId, {
record_type: "A",
name: hostname,
content: ip,
ttl: 1,
proxied: false
});
repos6.linkBindingRecord(db, bindingId, record.id);
recordId = record.id;
}
}
if (primaryId == null) primaryId = recordId;
}
repos6.setBindingDnsRecordId(db, bindingId, primaryId);
}
async function cleanupBindingDns(db, cf, bindingId, domainId, hostname) {
await syncBindingDns(db, cf, bindingId, domainId, hostname, []);
await syncBindingDns(db, cf, bindingId, domainId, hostname, [], null);
}
async function cleanupServiceDnsOnly(db, cf, serviceId) {
const bindings = repos6.listBindingsByService(db, serviceId);
@@ -1080,6 +1202,7 @@ function validateTargetIpsInPool(targetIps, ips) {
}
}
function bindingTargetIps(input) {
if (input.target_cname?.trim()) return [];
const raw = input.target_ips ? input.target_ips : input.target_ip?.trim() ? [input.target_ip.trim()] : [];
const normalized = normalizeIps(raw);
if (raw.length > 0 && normalized.length === 0) {
@@ -1087,20 +1210,150 @@ function bindingTargetIps(input) {
}
return normalized;
}
async function syncServiceBindingsToDns(db, cf, serviceId) {
const ips = repos6.listServiceIps(db, serviceId);
if (ips.length === 0) {
throw AppError.validation("\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432 \u043F\u0443\u043B \u0441\u0435\u0440\u0432\u0438\u0441\u0430");
function bindingTargetCname(input) {
const target = input.target_cname?.trim();
return target ? target : null;
}
function normalizeCnameTarget(target, zoneName) {
const trimmed = target.trim().toLowerCase();
if (!trimmed) {
throw AppError.validation("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 CNAME-\u0446\u0435\u043B\u044C");
}
if (trimmed.includes(".")) return trimmed;
return `${trimmed}.${zoneName.toLowerCase()}`;
}
function cnameContentMatches(left, right, zoneName) {
return normalizeCnameTarget(left, zoneName) === normalizeCnameTarget(right, zoneName);
}
function dnsHostnameMatches(recordName, hostname, zoneName) {
const label = dnsNameToSubdomainLabel2(recordName, zoneName);
if (label != null) return label === hostname;
return recordName === hostname;
}
function findLocalDnsRecord(db, domainId, zoneName, hostname, recordType, content) {
const records = repos6.listDnsByDomain(db, domainId);
return records.find(
(record) => record.record_type.toUpperCase() === recordType && (content == null || record.content === content) && dnsHostnameMatches(record.name, hostname, zoneName)
) ?? null;
}
async function findOrImportDnsRecord(db, cf, domainId, zoneName, hostname, recordType, content) {
const local = findLocalDnsRecord(
db,
domainId,
zoneName,
hostname,
recordType,
content
);
if (local) return local;
const domain = repos6.getDomain(db, domainId);
const remote = await cf.listDnsRecords(domain.cf_zone_id);
for (const cfRec of remote) {
if (cfRec.type.toUpperCase() !== recordType) continue;
if (content != null) {
if (recordType === "CNAME") {
if (!cnameContentMatches(cfRec.content, content, zoneName)) continue;
} else if (cfRec.content !== content) {
continue;
}
}
if (!dnsHostnameMatches(cfRec.name, hostname, zoneName)) continue;
if (!cfRec.id) continue;
const existing = repos6.findDnsByCfId(db, domainId, cfRec.id);
if (existing) return existing;
return repos6.insertDnsRecord(
db,
domainId,
cfRec.type,
cfRec.name,
cfRec.content,
cfRec.ttl,
cfRec.proxied ?? false,
cfRec.priority ?? null,
SYNC_SYNCED3,
"cloudflare",
cfRec.id
);
}
return null;
}
async function findOrImportDnsARecord(db, cf, domainId, zoneName, hostname, content) {
return findOrImportDnsRecord(
db,
cf,
domainId,
zoneName,
hostname,
"A",
content
);
}
async function serviceBindingsExistInDns(db, cf, serviceId) {
const bindings = repos6.listBindingsByService(db, serviceId);
if (bindings.length === 0) return false;
for (const binding of bindings) {
const cnameTarget = binding.cname_target?.trim() || null;
if (cnameTarget) {
const record = await findOrImportDnsRecord(
db,
cf,
binding.domain_id,
binding.zone_name,
binding.hostname,
"CNAME",
cnameTarget
);
if (!record) return false;
continue;
}
const targetIps = repos6.listBindingIps(db, binding.id);
if (targetIps.length === 0) return false;
for (const ip of targetIps) {
const record = await findOrImportDnsARecord(
db,
cf,
binding.domain_id,
binding.zone_name,
binding.hostname,
ip
);
if (!record) return false;
}
}
return true;
}
async function syncServiceBindingsToDns(db, cf, serviceId) {
const bindings = repos6.listBindingsByService(db, serviceId);
if (bindings.length === 0) {
throw AppError.validation("\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u0442\u0435 FQDN \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430");
}
const needsIpPool = bindings.some((binding) => {
if (binding.cname_target?.trim()) return false;
const targetIps = repos6.listBindingIps(db, binding.id);
return targetIps.length > 0;
});
const ips = repos6.listServiceIps(db, serviceId);
if (needsIpPool && ips.length === 0) {
throw AppError.validation("\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0432 \u043F\u0443\u043B \u0441\u0435\u0440\u0432\u0438\u0441\u0430");
}
for (const binding of bindings) {
const cnameTarget = binding.cname_target?.trim() || null;
if (cnameTarget) {
await syncBindingDns(
db,
cf,
binding.id,
binding.domain_id,
binding.hostname,
[],
cnameTarget
);
continue;
}
const targetIps = repos6.listBindingIps(db, binding.id);
if (targetIps.length === 0) {
throw AppError.validation(
`\u0443\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0434\u043B\u044F ${fqdnToDisplay(binding.hostname, binding.zone_name)}`
`\u0443\u043A\u0430\u0436\u0438\u0442\u0435 IP \u0438\u043B\u0438 CNAME \u0434\u043B\u044F ${fqdnToDisplay(binding.hostname, binding.zone_name)}`
);
}
validateTargetIpsInPool(targetIps, ips);
@@ -1110,7 +1363,8 @@ async function syncServiceBindingsToDns(db, cf, serviceId) {
binding.id,
binding.domain_id,
binding.hostname,
targetIps
targetIps,
null
);
}
}
@@ -1152,6 +1406,19 @@ async function syncGroupDomainDnsRecords(db, cf, groupId, domainId, hostname, de
}
continue;
}
const domain = repos6.getDomain(db, domainId);
const adopted = await findOrImportDnsARecord(
db,
cf,
domainId,
domain.zone_name,
hostname,
ip
);
if (adopted) {
repos6.linkGroupDnsRecord(db, groupId, adopted.id);
continue;
}
const record = await create(db, cf, domainId, {
record_type: "A",
name: hostname,
@@ -1273,13 +1540,21 @@ async function updateConfig(db, cf, id, req) {
for (const input of req.domains) {
const fqdn = input.fqdn.trim();
if (!fqdn) continue;
const targetCname = bindingTargetCname(input);
const targetIps = bindingTargetIps(input);
validateTargetIpsInPool(targetIps, ips);
if (!targetCname) {
validateTargetIpsInPool(targetIps, ips);
} else if (targetIps.length > 0) {
throw AppError.validation(
`\u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u043B\u0438\u0431\u043E IP, \u043B\u0438\u0431\u043E CNAME \u0434\u043B\u044F ${fqdn}`
);
}
const { zoneName, hostname } = parseFqdn(fqdn, knownZones);
const domainId = await resolveDomainId(db, cf, zoneName);
const binding = repos6.findBinding(db, id, domainId, hostname) ?? repos6.insertBinding(db, domainId, id, hostname, null);
keptBindingIds.push(binding.id);
repos6.replaceBindingIps(db, binding.id, targetIps);
repos6.replaceBindingIps(db, binding.id, targetCname ? [] : targetIps);
repos6.setBindingCnameTarget(db, binding.id, targetCname);
if (pushDns) {
await syncBindingDns(
db,
@@ -1287,7 +1562,8 @@ async function updateConfig(db, cf, id, req) {
binding.id,
domainId,
hostname,
targetIps
targetIps,
targetCname
);
}
}
@@ -1320,6 +1596,10 @@ async function updateConfig(db, cf, id, req) {
if (shouldPushDns(db, service)) {
await syncServiceBindingsToDns(db, cf, id);
await syncGroupDomainForService(db, cf, id);
} else if (req.domains && req.domains.length > 0 && !service.enabled && await serviceBindingsExistInDns(db, cf, id)) {
repos6.setServiceEnabled(db, id, true);
await syncServiceBindingsToDns(db, cf, id);
await syncGroupDomainForService(db, cf, id);
}
return buildView(db, id);
}
@@ -1343,7 +1623,7 @@ async function updateGroup2(db, cf, id, body) {
await cleanupGroupDomainDns(db, cf, id);
}
const domain = await normalizeGroupDomain(db, cf, body.domain);
const group = repos6.updateServiceGroup(
let group = repos6.updateServiceGroup(
db,
id,
body.name,
@@ -1351,6 +1631,10 @@ async function updateGroup2(db, cf, id, body) {
body.icon ?? null,
domain
);
if (!domain && group.enabled) {
repos6.setServiceGroupEnabled(db, id, false);
group = repos6.getServiceGroup(db, id);
}
await syncEnabledServicesInGroup(db, cf, id);
return group;
}
@@ -1361,12 +1645,9 @@ async function toggleService(db, cf, serviceId, enabled) {
const service = repos6.getService(db, serviceId);
if (enabled && service.service_group_id) {
const group = repos6.getServiceGroup(db, service.service_group_id);
if (!group.enabled) {
if (group.domain?.trim() && !group.enabled) {
throw AppError.validation("\u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0433\u0440\u0443\u043F\u043F\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432");
}
if (!group.domain?.trim()) {
throw AppError.validation("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u0434\u043E\u043C\u0435\u043D \u0443 \u0433\u0440\u0443\u043F\u043F\u044B \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432");
}
}
repos6.setServiceEnabled(db, serviceId, enabled);
if (!enabled) {
@@ -1379,6 +1660,10 @@ async function toggleService(db, cf, serviceId, enabled) {
return buildView(db, serviceId);
}
async function toggleGroup(db, cf, groupId, enabled) {
const group = repos6.getServiceGroup(db, groupId);
if (enabled && !group.domain?.trim()) {
throw AppError.validation("\u043D\u0435\u043B\u044C\u0437\u044F \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0433\u0440\u0443\u043F\u043F\u0443 \u0431\u0435\u0437 \u0434\u043E\u043C\u0435\u043D\u0430");
}
repos6.setServiceGroupEnabled(db, groupId, enabled);
if (!enabled) {
const services = repos6.listServicesByGroup(db, groupId);
@@ -1394,6 +1679,12 @@ async function toggleGroup(db, cf, groupId, enabled) {
}
return listGroupViews(db);
}
function reorderServices(db, groupId, serviceIds) {
if (groupId !== null) {
repos6.getServiceGroup(db, groupId);
}
repos6.reorderServices(db, groupId, serviceIds);
}
// src/routes/services.ts
async function serviceRoutes(app2) {
@@ -1405,6 +1696,15 @@ async function serviceRoutes(app2) {
app2.get("/services", async (request) => {
return listViews(request.server.db);
});
app2.patch("/services/reorder", async (request) => {
const body = reorderServicesSchema.parse(request.body);
reorderServices(
request.server.db,
body.group_id,
body.service_ids
);
return { ok: true };
});
app2.post("/services", async (request) => {
const body = createSchema.parse(request.body);
const service = repos7.createService(
@@ -1452,14 +1752,9 @@ async function serviceRoutes(app2) {
}
// src/routes/service-groups.ts
import { z as z4 } from "zod";
import { createServiceGroupSchema, toggleEnabledSchema } from "@cfdm/shared";
async function serviceGroupRoutes(app2) {
const bodySchema = z4.object({
name: z4.string(),
type: z4.string().optional(),
icon: z4.string().optional(),
domain: z4.string().optional()
});
const bodySchema = createServiceGroupSchema;
app2.get("/service-groups", async (request) => {
return listGroupViews(request.server.db);
});
@@ -1488,7 +1783,7 @@ async function serviceGroupRoutes(app2) {
});
app2.patch("/service-groups/:id/toggle", async (request) => {
const { id } = request.params;
const body = z4.object({ enabled: z4.boolean() }).parse(request.body);
const body = toggleEnabledSchema.parse(request.body);
return toggleGroup(
request.server.db,
request.server.cf,
@@ -1499,18 +1794,18 @@ async function serviceGroupRoutes(app2) {
}
// src/routes/service-bindings.ts
import { z as z5 } from "zod";
import { z as z4 } from "zod";
async function serviceBindingRoutes(app2) {
const createSchema = z5.object({
domain_id: z5.number(),
service_id: z5.number(),
hostname: z5.string().optional(),
target_ip: z5.string().optional()
const createSchema = z4.object({
domain_id: z4.number(),
service_id: z4.number(),
hostname: z4.string().optional(),
target_ip: z4.string().optional()
});
const updateSchema = z5.object({
service_id: z5.number().optional(),
hostname: z5.string().optional(),
target_ip: z5.string().optional()
const updateSchema = z4.object({
service_id: z4.number().optional(),
hostname: z4.string().optional(),
target_ip: z4.string().optional()
});
app2.get("/service-bindings", async (request) => {
return listAll(request.server.db);
@@ -1550,15 +1845,15 @@ async function serviceBindingRoutes(app2) {
}
// src/routes/domains.ts
import { z as z6 } from "zod";
import { z as z5 } from "zod";
async function domainRoutes(app2) {
const createSchema = z6.object({
zone_name: z6.string(),
group_id: z6.number().nullable().optional()
const createSchema = z5.object({
zone_name: z5.string(),
group_id: z5.number().nullable().optional()
});
const updateSchema = z6.object({
group_id: z6.number().nullable().optional(),
status: z6.string().optional()
const updateSchema = z5.object({
group_id: z5.number().nullable().optional(),
status: z5.string().optional()
});
app2.get("/domains", async (request) => {
const query = request.query;
@@ -1605,7 +1900,7 @@ async function domainRoutes(app2) {
});
app2.put("/domains/:id/services", async (request) => {
const { id } = request.params;
const body = z6.object({ service_ids: z6.array(z6.number()) }).parse(request.body);
const body = z5.object({ service_ids: z5.array(z5.number()) }).parse(request.body);
const serviceIds = await setDomainServices2(
request.server.db,
Number(id),
@@ -1616,15 +1911,15 @@ async function domainRoutes(app2) {
}
// src/routes/dns.ts
import { z as z7 } from "zod";
import { z as z6 } from "zod";
async function dnsRoutes(app2) {
const createSchema = z7.object({
record_type: z7.string(),
name: z7.string(),
content: z7.string(),
ttl: z7.number().optional(),
proxied: z7.boolean().optional(),
priority: z7.number().optional()
const createSchema = z6.object({
record_type: z6.string(),
name: z6.string(),
content: z6.string(),
ttl: z6.number().optional(),
proxied: z6.boolean().optional(),
priority: z6.number().optional()
});
app2.get("/domains/:id/dns", async (request) => {
const { id } = request.params;
@@ -1653,7 +1948,7 @@ async function dnsRoutes(app2) {
});
app2.post("/domains/:id/dns/bulk", async (request) => {
const { id } = request.params;
const body = z7.object({ operations: z7.array(z7.record(z7.unknown())) }).parse(request.body);
const body = z6.object({ operations: z6.array(z6.record(z6.unknown())) }).parse(request.body);
return bulk(
request.server.db,
request.server.cf,
@@ -1691,7 +1986,7 @@ async function dnsRoutes(app2) {
});
app2.post("/domains/:id/dns/:recordId/resolve", async (request) => {
const { id, recordId } = request.params;
const body = z7.object({ source: z7.string() }).parse(request.body);
const body = z6.object({ source: z6.string() }).parse(request.body);
return resolveConflict(
request.server.db,
request.server.cf,
@@ -1703,8 +1998,9 @@ async function dnsRoutes(app2) {
}
// src/routes/subdomains.ts
import { z as z8 } from "zod";
import { updateSubdomainSchema } from "@cfdm/shared";
import { repos as repos8 } from "@cfdm/db";
import { z as z7 } from "zod";
async function subdomainRoutes(app2) {
app2.get("/domains/:id/subdomains", async (request) => {
const { id } = request.params;
@@ -1713,7 +2009,7 @@ async function subdomainRoutes(app2) {
});
app2.post("/domains/:id/subdomains", async (request) => {
const { id } = request.params;
const body = z8.object({ name: z8.string() }).parse(request.body);
const body = z7.object({ name: z7.string() }).parse(request.body);
const domain = repos8.getDomain(request.server.db, Number(id));
const fqdn = body.name === "@" ? domain.zone_name : `${body.name}.${domain.zone_name}`;
return repos8.createSubdomain(
@@ -1729,16 +2025,18 @@ async function subdomainRoutes(app2) {
});
app2.patch("/subdomains/:id", async (request) => {
const { id } = request.params;
const body = z8.object({ name: z8.string() }).parse(request.body);
const body = updateSubdomainSchema.parse(request.body);
const sub = repos8.getSubdomain(request.server.db, Number(id));
const domain = repos8.getDomain(request.server.db, sub.domain_id);
const fqdn = `${body.name}.${domain.zone_name}`;
return repos8.updateSubdomain(
request.server.db,
Number(id),
body.name,
fqdn
);
const patch = {};
if (body.name !== void 0) {
patch.name = body.name;
patch.fqdn = body.name === "@" ? domain.zone_name : `${body.name}.${domain.zone_name}`;
}
if (body.enabled !== void 0) {
patch.enabled = body.enabled;
}
return repos8.updateSubdomain(request.server.db, Number(id), patch);
});
app2.delete("/subdomains/:id", async (request) => {
const { id } = request.params;
@@ -1896,6 +2194,7 @@ async function syncRoutes(app2) {
}
// src/app.ts
import { AsyncTask, CronJob } from "toad-scheduler";
async function buildApp(opts = {}) {
const config2 = opts.config ?? loadConfig();
const app2 = Fastify({
@@ -1943,20 +2242,23 @@ async function buildApp(opts = {}) {
}
if (!opts.memory) {
await app2.register(import("@fastify/schedule"));
app2.scheduler.addCronJob(
{
cronExpression: config2.certCheckCron,
name: "certificate-check"
},
const certTask = new AsyncTask(
"certificate-check",
async () => {
try {
const n = await runAllChecks(app2.db);
app2.log.info({ checked: n }, "certificate check completed");
} catch (err) {
app2.log.warn({ err }, "certificate check failed");
}
const n = await runAllChecks(app2.db);
app2.log.info({ checked: n }, "certificate check completed");
},
(err) => {
app2.log.warn({ err }, "certificate check failed");
}
);
app2.scheduler.addCronJob(
new CronJob(
{ cronExpression: config2.certCheckCron },
certTask,
{ preventOverrun: true }
)
);
}
return app2;
}