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
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:
Vendored
+117
-14
@@ -29,6 +29,7 @@ var services = sqliteTable("services", {
|
||||
),
|
||||
subdomain: text("subdomain"),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
|
||||
sort_order: integer("sort_order").notNull().default(0),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
@@ -50,6 +51,7 @@ var domains = sqliteTable("domains", {
|
||||
zone_name: text("zone_name").notNull().unique(),
|
||||
cf_zone_id: text("cf_zone_id").notNull(),
|
||||
status: text("status").notNull().default("active"),
|
||||
cert_monitoring: text("cert_monitoring").notNull().default("auto"),
|
||||
last_synced_at: text("last_synced_at"),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
@@ -59,6 +61,8 @@ var subdomains = sqliteTable("subdomains", {
|
||||
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
fqdn: text("fqdn").notNull(),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
cert_monitoring: text("cert_monitoring").notNull().default("auto"),
|
||||
created_at: text("created_at").notNull().default(sql`datetime('now')`),
|
||||
updated_at: text("updated_at").notNull().default(sql`datetime('now')`)
|
||||
});
|
||||
@@ -83,6 +87,7 @@ var serviceBindings = sqliteTable("service_bindings", {
|
||||
domain_id: integer("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
|
||||
service_id: integer("service_id").notNull().references(() => services.id, { onDelete: "cascade" }),
|
||||
hostname: text("hostname").notNull().default("@"),
|
||||
cname_target: text("cname_target"),
|
||||
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
|
||||
onDelete: "set null"
|
||||
}),
|
||||
@@ -234,6 +239,7 @@ __export(repos_exports, {
|
||||
createSyncJob: () => createSyncJob,
|
||||
deleteBinding: () => deleteBinding,
|
||||
deleteBindingsExcept: () => deleteBindingsExcept,
|
||||
deleteCertificatesNotIn: () => deleteCertificatesNotIn,
|
||||
deleteDnsRecord: () => deleteDnsRecord,
|
||||
deleteDomain: () => deleteDomain,
|
||||
deleteGroup: () => deleteGroup,
|
||||
@@ -243,6 +249,7 @@ __export(repos_exports, {
|
||||
findBinding: () => findBinding,
|
||||
findDnsByCfId: () => findDnsByCfId,
|
||||
findDomainByZoneName: () => findDomainByZoneName,
|
||||
findSubdomainByDomainAndName: () => findSubdomainByDomainAndName,
|
||||
finishSyncJob: () => finishSyncJob,
|
||||
getBinding: () => getBinding,
|
||||
getBindingView: () => getBindingView,
|
||||
@@ -280,8 +287,10 @@ __export(repos_exports, {
|
||||
listSubdomainsByDomain: () => listSubdomainsByDomain,
|
||||
listUngroupedServices: () => listUngroupedServices,
|
||||
markDnsPendingDelete: () => markDnsPendingDelete,
|
||||
reorderServices: () => reorderServices,
|
||||
replaceBindingIps: () => replaceBindingIps,
|
||||
replaceServiceIps: () => replaceServiceIps,
|
||||
setBindingCnameTarget: () => setBindingCnameTarget,
|
||||
setBindingDnsRecordId: () => setBindingDnsRecordId,
|
||||
setDnsSyncStatus: () => setDnsSyncStatus,
|
||||
setDomainLastSynced: () => setDomainLastSynced,
|
||||
@@ -300,7 +309,8 @@ __export(repos_exports, {
|
||||
upsertCertificateCheck: () => upsertCertificateCheck,
|
||||
upsertSubdomain: () => upsertSubdomain
|
||||
});
|
||||
import { and, asc, count, eq, isNull, like, or, sql as sql2 } from "drizzle-orm";
|
||||
import { dnsRecordNamesMatch } from "@cfdm/shared";
|
||||
import { and, asc, count, eq, isNull, like, notInArray, or, sql as sql2 } from "drizzle-orm";
|
||||
function listGroups(db) {
|
||||
return db.select().from(groups).orderBy(asc(groups.name)).all();
|
||||
}
|
||||
@@ -367,12 +377,16 @@ function createDomain(db, groupId, zoneName, cfZoneId) {
|
||||
}).returning({ id: domains.id }).get().id;
|
||||
return getDomain(db, id);
|
||||
}
|
||||
function updateDomain(db, id, groupId, status) {
|
||||
const result = db.update(domains).set({
|
||||
function updateDomain(db, id, groupId, status, certMonitoring) {
|
||||
const updates = {
|
||||
group_id: groupId,
|
||||
status,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq(domains.id, id)).run();
|
||||
};
|
||||
if (certMonitoring !== void 0) {
|
||||
updates.cert_monitoring = certMonitoring;
|
||||
}
|
||||
const result = db.update(domains).set(updates).where(eq(domains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`domain ${id}`);
|
||||
return getDomain(db, id);
|
||||
}
|
||||
@@ -397,6 +411,10 @@ function getSubdomain(db, id) {
|
||||
if (!row) throw new NotFoundError(`subdomain ${id}`);
|
||||
return row;
|
||||
}
|
||||
function findSubdomainByDomainAndName(db, domainId, name) {
|
||||
const row = db.select().from(subdomains).where(and(eq(subdomains.domain_id, domainId), eq(subdomains.name, name))).get();
|
||||
return row ?? null;
|
||||
}
|
||||
function upsertSubdomain(db, domainId, name, fqdn) {
|
||||
db.run(sql2`
|
||||
INSERT INTO subdomains (domain_id, name, fqdn)
|
||||
@@ -410,8 +428,15 @@ function createSubdomain(db, domainId, name, fqdn) {
|
||||
const id = db.insert(subdomains).values({ domain_id: domainId, name, fqdn }).returning({ id: subdomains.id }).get().id;
|
||||
return getSubdomain(db, id);
|
||||
}
|
||||
function updateSubdomain(db, id, name, fqdn) {
|
||||
const result = db.update(subdomains).set({ name, fqdn, updated_at: sql2`datetime('now')` }).where(eq(subdomains.id, id)).run();
|
||||
function updateSubdomain(db, id, patch) {
|
||||
const updates = { updated_at: sql2`datetime('now')` };
|
||||
if (patch.name !== void 0) updates.name = patch.name;
|
||||
if (patch.fqdn !== void 0) updates.fqdn = patch.fqdn;
|
||||
if (patch.enabled !== void 0) updates.enabled = patch.enabled;
|
||||
if (patch.cert_monitoring !== void 0) {
|
||||
updates.cert_monitoring = patch.cert_monitoring;
|
||||
}
|
||||
const result = db.update(subdomains).set(updates).where(eq(subdomains.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`subdomain ${id}`);
|
||||
return getSubdomain(db, id);
|
||||
}
|
||||
@@ -518,14 +543,19 @@ function findDnsByCfId(db, domainId, cfRecordId) {
|
||||
function markDnsPendingDelete(db, id) {
|
||||
setDnsSyncStatus(db, id, "pending_delete", null, null);
|
||||
}
|
||||
function maxSortOrderInGroup(db, groupId) {
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq(services.service_group_id, groupId);
|
||||
const row = db.select({ maxOrder: sql2`coalesce(max(${services.sort_order}), -1)` }).from(services).where(condition).get();
|
||||
return row?.maxOrder ?? -1;
|
||||
}
|
||||
function listServices(db) {
|
||||
return db.select().from(services).orderBy(asc(services.name)).all();
|
||||
return db.select().from(services).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function listServicesByGroup(db, groupId) {
|
||||
return db.select().from(services).where(eq(services.service_group_id, groupId)).orderBy(asc(services.name)).all();
|
||||
return db.select().from(services).where(eq(services.service_group_id, groupId)).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function listUngroupedServices(db) {
|
||||
return db.select().from(services).where(isNull(services.service_group_id)).orderBy(asc(services.name)).all();
|
||||
return db.select().from(services).where(isNull(services.service_group_id)).orderBy(asc(services.sort_order), asc(services.name)).all();
|
||||
}
|
||||
function getService(db, id) {
|
||||
const row = db.select().from(services).where(eq(services.id, id)).get();
|
||||
@@ -533,7 +563,8 @@ function getService(db, id) {
|
||||
return row;
|
||||
}
|
||||
function createService(db, name, slug) {
|
||||
const id = db.insert(services).values({ name, slug, subdomain: slug }).returning({ id: services.id }).get().id;
|
||||
const sortOrder = maxSortOrderInGroup(db, null) + 1;
|
||||
const id = db.insert(services).values({ name, slug, subdomain: slug, sort_order: sortOrder }).returning({ id: services.id }).get().id;
|
||||
return getService(db, id);
|
||||
}
|
||||
function updateService(db, id, name, slug) {
|
||||
@@ -550,7 +581,31 @@ function setServiceEnabled(db, id, enabled) {
|
||||
return getService(db, id);
|
||||
}
|
||||
function setServiceGroup(db, id, groupId) {
|
||||
db.update(services).set({ service_group_id: groupId, updated_at: sql2`datetime('now')` }).where(eq(services.id, id)).run();
|
||||
const sortOrder = maxSortOrderInGroup(db, groupId) + 1;
|
||||
db.update(services).set({
|
||||
service_group_id: groupId,
|
||||
sort_order: sortOrder,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq(services.id, id)).run();
|
||||
}
|
||||
function reorderServices(db, groupId, orderedIds) {
|
||||
const uniqueIds = new Set(orderedIds);
|
||||
if (uniqueIds.size !== orderedIds.length) {
|
||||
throw new Error("duplicate service ids in reorder request");
|
||||
}
|
||||
const condition = groupId === null ? isNull(services.service_group_id) : eq(services.service_group_id, groupId);
|
||||
const existing = db.select({ id: services.id }).from(services).where(condition).all().map((row) => row.id);
|
||||
const existingSet = new Set(existing);
|
||||
for (const serviceId of orderedIds) {
|
||||
if (!existingSet.has(serviceId)) {
|
||||
throw new NotFoundError(`service ${serviceId} not in group`);
|
||||
}
|
||||
}
|
||||
db.transaction((tx) => {
|
||||
for (let index = 0; index < orderedIds.length; index++) {
|
||||
tx.update(services).set({ sort_order: index, updated_at: sql2`datetime('now')` }).where(eq(services.id, orderedIds[index])).run();
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteService(db, id) {
|
||||
const result = db.delete(services).where(eq(services.id, id)).run();
|
||||
@@ -618,6 +673,12 @@ function replaceBindingIps(db, bindingId, ips) {
|
||||
db.insert(serviceBindingIps).values({ binding_id: bindingId, ip }).run();
|
||||
}
|
||||
}
|
||||
function setBindingCnameTarget(db, bindingId, target) {
|
||||
db.update(serviceBindings).set({
|
||||
cname_target: target,
|
||||
updated_at: sql2`datetime('now')`
|
||||
}).where(eq(serviceBindings.id, bindingId)).run();
|
||||
}
|
||||
function listRecordsForBinding(db, bindingId) {
|
||||
return db.all(sql2`
|
||||
SELECT dr.* FROM dns_records dr
|
||||
@@ -662,6 +723,40 @@ function unlinkGroupDnsRecord(db, groupId, dnsRecordId) {
|
||||
)
|
||||
).run();
|
||||
}
|
||||
function dnsRecordMatchesHostname(recordName, hostname, zoneName) {
|
||||
return dnsRecordNamesMatch(recordName, hostname, zoneName);
|
||||
}
|
||||
function enrichServiceBindingView(db, row) {
|
||||
const configured = listBindingIps(db, row.id);
|
||||
const linkedRecords = listRecordsForBinding(db, row.id);
|
||||
const linkedIps = linkedRecords.filter((record) => record.record_type.toUpperCase() === "A").map((record) => record.content);
|
||||
const target_ips = [
|
||||
.../* @__PURE__ */ new Set([
|
||||
...configured,
|
||||
...linkedIps,
|
||||
...row.target_ip ? [row.target_ip] : []
|
||||
])
|
||||
].sort();
|
||||
if (target_ips.length === 0) {
|
||||
for (const record of listDnsByDomain(db, row.domain_id)) {
|
||||
if (record.record_type.toUpperCase() !== "A") continue;
|
||||
if (!dnsRecordMatchesHostname(record.name, row.hostname, row.zone_name)) {
|
||||
continue;
|
||||
}
|
||||
if (!target_ips.includes(record.content)) {
|
||||
target_ips.push(record.content);
|
||||
}
|
||||
}
|
||||
target_ips.sort();
|
||||
}
|
||||
const sync_status = row.sync_status ?? linkedRecords.find((record) => record.sync_status)?.sync_status ?? null;
|
||||
return {
|
||||
...row,
|
||||
target_ips,
|
||||
target_ip: target_ips[0] ?? null,
|
||||
sync_status
|
||||
};
|
||||
}
|
||||
function listAllBindings(db) {
|
||||
return db.all(sql2`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
@@ -675,7 +770,7 @@ function listAllBindings(db) {
|
||||
JOIN services s ON s.id = sb.service_id
|
||||
LEFT JOIN dns_records dr ON dr.id = sb.dns_record_id
|
||||
ORDER BY d.zone_name, s.name
|
||||
`);
|
||||
`).map((row) => enrichServiceBindingView(db, row));
|
||||
}
|
||||
function listBindingsByDomain(db, domainId) {
|
||||
return db.all(sql2`
|
||||
@@ -691,7 +786,7 @@ function listBindingsByDomain(db, domainId) {
|
||||
LEFT JOIN dns_records dr ON dr.id = sb.dns_record_id
|
||||
WHERE sb.domain_id = ${domainId}
|
||||
ORDER BY s.name
|
||||
`);
|
||||
`).map((row) => enrichServiceBindingView(db, row));
|
||||
}
|
||||
function listBindingsByService(db, serviceId) {
|
||||
return db.all(sql2`
|
||||
@@ -720,7 +815,7 @@ function getBindingView(db, id) {
|
||||
WHERE sb.id = ${id}
|
||||
`);
|
||||
if (!rows[0]) throw new NotFoundError(`service binding ${id}`);
|
||||
return rows[0];
|
||||
return enrichServiceBindingView(db, rows[0]);
|
||||
}
|
||||
function findBinding(db, serviceId, domainId, hostname) {
|
||||
const row = db.select().from(serviceBindings).where(
|
||||
@@ -814,6 +909,14 @@ function countCertificatesByStatus(db) {
|
||||
}).from(certificates).groupBy(certificates.status).all();
|
||||
return rows.map((r) => [r.status, r.cnt]);
|
||||
}
|
||||
function deleteCertificatesNotIn(db, hostnames) {
|
||||
if (hostnames.length === 0) {
|
||||
const result2 = db.delete(certificates).run();
|
||||
return result2.changes;
|
||||
}
|
||||
const result = db.delete(certificates).where(notInArray(certificates.hostname, hostnames)).run();
|
||||
return result.changes;
|
||||
}
|
||||
function createSyncJob(db, id, domainId) {
|
||||
db.insert(syncJobs).values({ id, domain_id: domainId, status: "pending" }).run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user