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
+163
-4
@@ -215,6 +215,23 @@ declare const services: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
sort_order: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "sort_order";
|
||||
tableName: "services";
|
||||
dataType: "number";
|
||||
columnType: "SQLiteInteger";
|
||||
data: number;
|
||||
driverParam: number;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
created_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "created_at";
|
||||
tableName: "services";
|
||||
@@ -506,6 +523,25 @@ declare const domains: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cert_monitoring: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cert_monitoring";
|
||||
tableName: "domains";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
last_synced_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "last_synced_at";
|
||||
tableName: "domains";
|
||||
@@ -642,6 +678,23 @@ declare const subdomains: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
enabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "enabled";
|
||||
tableName: "subdomains";
|
||||
dataType: "boolean";
|
||||
columnType: "SQLiteBoolean";
|
||||
data: boolean;
|
||||
driverParam: number;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
created_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "created_at";
|
||||
tableName: "subdomains";
|
||||
@@ -1020,6 +1073,25 @@ declare const serviceBindings: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cname_target: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cname_target";
|
||||
tableName: "service_bindings";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: false;
|
||||
hasDefault: false;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
dns_record_id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "dns_record_id";
|
||||
tableName: "service_bindings";
|
||||
@@ -1805,6 +1877,23 @@ declare const schema: {
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
sort_order: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "sort_order";
|
||||
tableName: "services";
|
||||
dataType: "number";
|
||||
columnType: "SQLiteInteger";
|
||||
data: number;
|
||||
driverParam: number;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
created_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "created_at";
|
||||
tableName: "services";
|
||||
@@ -2096,6 +2185,25 @@ declare const schema: {
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cert_monitoring: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cert_monitoring";
|
||||
tableName: "domains";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
last_synced_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "last_synced_at";
|
||||
tableName: "domains";
|
||||
@@ -2232,6 +2340,23 @@ declare const schema: {
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
enabled: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "enabled";
|
||||
tableName: "subdomains";
|
||||
dataType: "boolean";
|
||||
columnType: "SQLiteBoolean";
|
||||
data: boolean;
|
||||
driverParam: number;
|
||||
notNull: true;
|
||||
hasDefault: true;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: undefined;
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
created_at: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "created_at";
|
||||
tableName: "subdomains";
|
||||
@@ -2610,6 +2735,25 @@ declare const schema: {
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
cname_target: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "cname_target";
|
||||
tableName: "service_bindings";
|
||||
dataType: "string";
|
||||
columnType: "SQLiteText";
|
||||
data: string;
|
||||
driverParam: string;
|
||||
notNull: false;
|
||||
hasDefault: false;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: [string, ...string[]];
|
||||
baseColumn: never;
|
||||
identity: undefined;
|
||||
generated: undefined;
|
||||
}, {}, {
|
||||
length: number | undefined;
|
||||
}>;
|
||||
dns_record_id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
||||
name: "dns_record_id";
|
||||
tableName: "service_bindings";
|
||||
@@ -3227,15 +3371,22 @@ declare function listDomainsEnriched(db: Db, groupId?: number): DomainListItem[]
|
||||
declare function findDomainByZoneName(db: Db, zoneName: string): Domain | null;
|
||||
declare function getDomain(db: Db, id: number): Domain;
|
||||
declare function createDomain(db: Db, groupId: number | null, zoneName: string, cfZoneId: string): Domain;
|
||||
declare function updateDomain(db: Db, id: number, groupId: number | null, status: string): Domain;
|
||||
declare function updateDomain(db: Db, id: number, groupId: number | null, status: string, certMonitoring?: string): Domain;
|
||||
declare function deleteDomain(db: Db, id: number): void;
|
||||
declare function setDomainLastSynced(db: Db, id: number): void;
|
||||
declare function listAllDomains(db: Db): Domain[];
|
||||
declare function listSubdomainsByDomain(db: Db, domainId: number): Subdomain[];
|
||||
declare function getSubdomain(db: Db, id: number): Subdomain;
|
||||
declare function findSubdomainByDomainAndName(db: Db, domainId: number, name: string): Subdomain | null;
|
||||
declare function upsertSubdomain(db: Db, domainId: number, name: string, fqdn: string): void;
|
||||
declare function createSubdomain(db: Db, domainId: number, name: string, fqdn: string): Subdomain;
|
||||
declare function updateSubdomain(db: Db, id: number, name: string, fqdn: string): Subdomain;
|
||||
interface UpdateSubdomainPatch {
|
||||
name?: string;
|
||||
fqdn?: string;
|
||||
enabled?: boolean;
|
||||
cert_monitoring?: string;
|
||||
}
|
||||
declare function updateSubdomain(db: Db, id: number, patch: UpdateSubdomainPatch): Subdomain;
|
||||
declare function deleteSubdomain(db: Db, id: number): void;
|
||||
declare function listAllSubdomains(db: Db): Subdomain[];
|
||||
declare function listDnsRecords(db: Db, domainId: number, filter?: DnsListFilter): DnsRecord[];
|
||||
@@ -3255,6 +3406,7 @@ declare function createService(db: Db, name: string, slug: string): Service;
|
||||
declare function updateService(db: Db, id: number, name: string, slug: string): Service;
|
||||
declare function setServiceEnabled(db: Db, id: number, enabled: boolean): Service;
|
||||
declare function setServiceGroup(db: Db, id: number, groupId: number | null): void;
|
||||
declare function reorderServices(db: Db, groupId: number | null, orderedIds: number[]): void;
|
||||
declare function deleteService(db: Db, id: number): void;
|
||||
declare function listServiceGroups(db: Db): ServiceGroup[];
|
||||
declare function getServiceGroup(db: Db, id: number): ServiceGroup;
|
||||
@@ -3266,6 +3418,7 @@ declare function listServiceIps(db: Db, serviceId: number): string[];
|
||||
declare function replaceServiceIps(db: Db, serviceId: number, ips: string[]): void;
|
||||
declare function listBindingIps(db: Db, bindingId: number): string[];
|
||||
declare function replaceBindingIps(db: Db, bindingId: number, ips: string[]): void;
|
||||
declare function setBindingCnameTarget(db: Db, bindingId: number, target: string | null): void;
|
||||
declare function listRecordsForBinding(db: Db, bindingId: number): DnsRecord[];
|
||||
declare function linkBindingRecord(db: Db, bindingId: number, dnsRecordId: number): void;
|
||||
declare function unlinkBindingRecord(db: Db, bindingId: number, dnsRecordId: number): void;
|
||||
@@ -3290,11 +3443,13 @@ declare function listCertificates(db: Db, status?: string): Certificate[];
|
||||
declare function getCertificate(db: Db, id: number): Certificate;
|
||||
declare function upsertCertificateCheck(db: Db, domainId: number, subdomainId: number | null, hostname: string, expiresAt: string | null, status: string, lastError: string | null): Certificate;
|
||||
declare function countCertificatesByStatus(db: Db): Array<[string, number]>;
|
||||
declare function deleteCertificatesNotIn(db: Db, hostnames: string[]): number;
|
||||
declare function createSyncJob(db: Db, id: string, domainId: number | null): void;
|
||||
declare function getSyncJob(db: Db, id: string): SyncJob;
|
||||
declare function finishSyncJob(db: Db, id: string, status: string, message: string | null): void;
|
||||
|
||||
type repos_DnsListFilter = DnsListFilter;
|
||||
type repos_UpdateSubdomainPatch = UpdateSubdomainPatch;
|
||||
declare const repos_bindingsToRemove: typeof bindingsToRemove;
|
||||
declare const repos_countCertificatesByStatus: typeof countCertificatesByStatus;
|
||||
declare const repos_createDomain: typeof createDomain;
|
||||
@@ -3305,6 +3460,7 @@ declare const repos_createSubdomain: typeof createSubdomain;
|
||||
declare const repos_createSyncJob: typeof createSyncJob;
|
||||
declare const repos_deleteBinding: typeof deleteBinding;
|
||||
declare const repos_deleteBindingsExcept: typeof deleteBindingsExcept;
|
||||
declare const repos_deleteCertificatesNotIn: typeof deleteCertificatesNotIn;
|
||||
declare const repos_deleteDnsRecord: typeof deleteDnsRecord;
|
||||
declare const repos_deleteDomain: typeof deleteDomain;
|
||||
declare const repos_deleteGroup: typeof deleteGroup;
|
||||
@@ -3314,6 +3470,7 @@ declare const repos_deleteSubdomain: typeof deleteSubdomain;
|
||||
declare const repos_findBinding: typeof findBinding;
|
||||
declare const repos_findDnsByCfId: typeof findDnsByCfId;
|
||||
declare const repos_findDomainByZoneName: typeof findDomainByZoneName;
|
||||
declare const repos_findSubdomainByDomainAndName: typeof findSubdomainByDomainAndName;
|
||||
declare const repos_finishSyncJob: typeof finishSyncJob;
|
||||
declare const repos_getBinding: typeof getBinding;
|
||||
declare const repos_getBindingView: typeof getBindingView;
|
||||
@@ -3351,8 +3508,10 @@ declare const repos_listServicesByGroup: typeof listServicesByGroup;
|
||||
declare const repos_listSubdomainsByDomain: typeof listSubdomainsByDomain;
|
||||
declare const repos_listUngroupedServices: typeof listUngroupedServices;
|
||||
declare const repos_markDnsPendingDelete: typeof markDnsPendingDelete;
|
||||
declare const repos_reorderServices: typeof reorderServices;
|
||||
declare const repos_replaceBindingIps: typeof replaceBindingIps;
|
||||
declare const repos_replaceServiceIps: typeof replaceServiceIps;
|
||||
declare const repos_setBindingCnameTarget: typeof setBindingCnameTarget;
|
||||
declare const repos_setBindingDnsRecordId: typeof setBindingDnsRecordId;
|
||||
declare const repos_setDnsSyncStatus: typeof setDnsSyncStatus;
|
||||
declare const repos_setDomainLastSynced: typeof setDomainLastSynced;
|
||||
@@ -3371,7 +3530,7 @@ declare const repos_updateSubdomain: typeof updateSubdomain;
|
||||
declare const repos_upsertCertificateCheck: typeof upsertCertificateCheck;
|
||||
declare const repos_upsertSubdomain: typeof upsertSubdomain;
|
||||
declare namespace repos {
|
||||
export { type repos_DnsListFilter as DnsListFilter, repos_bindingsToRemove as bindingsToRemove, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createGroup as createGroup, repos_createService as createService, repos_createServiceGroup as createServiceGroup, repos_createSubdomain as createSubdomain, repos_createSyncJob as createSyncJob, repos_deleteBinding as deleteBinding, repos_deleteBindingsExcept as deleteBindingsExcept, repos_deleteDnsRecord as deleteDnsRecord, repos_deleteDomain as deleteDomain, repos_deleteGroup as deleteGroup, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_finishSyncJob as finishSyncJob, repos_getBinding as getBinding, repos_getBindingView as getBindingView, repos_getCertificate as getCertificate, repos_getDnsRecord as getDnsRecord, repos_getDomain as getDomain, repos_getGroup as getGroup, repos_getGroupWithStats as getGroupWithStats, repos_getService as getService, repos_getServiceGroup as getServiceGroup, repos_getSubdomain as getSubdomain, repos_getSyncJob as getSyncJob, repos_insertBinding as insertBinding, repos_insertDnsRecord as insertDnsRecord, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingsByDomain as listBindingsByDomain, repos_listBindingsByService as listBindingsByService, repos_listCertificates as listCertificates, repos_listDnsByDomain as listDnsByDomain, repos_listDnsRecords as listDnsRecords, repos_listDomains as listDomains, repos_listDomainsEnriched as listDomainsEnriched, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIps as listServiceIps, repos_listServices as listServices, repos_listServicesByGroup as listServicesByGroup, repos_listSubdomainsByDomain as listSubdomainsByDomain, repos_listUngroupedServices as listUngroupedServices, repos_markDnsPendingDelete as markDnsPendingDelete, repos_replaceBindingIps as replaceBindingIps, repos_replaceServiceIps as replaceServiceIps, repos_setBindingDnsRecordId as setBindingDnsRecordId, repos_setDnsSyncStatus as setDnsSyncStatus, repos_setDomainLastSynced as setDomainLastSynced, repos_setServiceEnabled as setServiceEnabled, repos_setServiceGroup as setServiceGroup, repos_setServiceGroupEnabled as setServiceGroupEnabled, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingFields as updateBindingFields, repos_updateDnsFields as updateDnsFields, repos_updateDomain as updateDomain, repos_updateGroup as updateGroup, repos_updateService as updateService, repos_updateServiceGroup as updateServiceGroup, repos_updateSubdomain as updateSubdomain, repos_upsertCertificateCheck as upsertCertificateCheck, repos_upsertSubdomain as upsertSubdomain };
|
||||
export { type repos_DnsListFilter as DnsListFilter, type repos_UpdateSubdomainPatch as UpdateSubdomainPatch, repos_bindingsToRemove as bindingsToRemove, repos_countCertificatesByStatus as countCertificatesByStatus, repos_createDomain as createDomain, repos_createGroup as createGroup, repos_createService as createService, repos_createServiceGroup as createServiceGroup, repos_createSubdomain as createSubdomain, repos_createSyncJob as createSyncJob, repos_deleteBinding as deleteBinding, repos_deleteBindingsExcept as deleteBindingsExcept, repos_deleteCertificatesNotIn as deleteCertificatesNotIn, repos_deleteDnsRecord as deleteDnsRecord, repos_deleteDomain as deleteDomain, repos_deleteGroup as deleteGroup, repos_deleteService as deleteService, repos_deleteServiceGroup as deleteServiceGroup, repos_deleteSubdomain as deleteSubdomain, repos_findBinding as findBinding, repos_findDnsByCfId as findDnsByCfId, repos_findDomainByZoneName as findDomainByZoneName, repos_findSubdomainByDomainAndName as findSubdomainByDomainAndName, repos_finishSyncJob as finishSyncJob, repos_getBinding as getBinding, repos_getBindingView as getBindingView, repos_getCertificate as getCertificate, repos_getDnsRecord as getDnsRecord, repos_getDomain as getDomain, repos_getGroup as getGroup, repos_getGroupWithStats as getGroupWithStats, repos_getService as getService, repos_getServiceGroup as getServiceGroup, repos_getSubdomain as getSubdomain, repos_getSyncJob as getSyncJob, repos_insertBinding as insertBinding, repos_insertDnsRecord as insertDnsRecord, repos_linkBindingRecord as linkBindingRecord, repos_linkGroupDnsRecord as linkGroupDnsRecord, repos_listAllBindings as listAllBindings, repos_listAllDomains as listAllDomains, repos_listAllSubdomains as listAllSubdomains, repos_listBindingIps as listBindingIps, repos_listBindingsByDomain as listBindingsByDomain, repos_listBindingsByService as listBindingsByService, repos_listCertificates as listCertificates, repos_listDnsByDomain as listDnsByDomain, repos_listDnsRecords as listDnsRecords, repos_listDomains as listDomains, repos_listDomainsEnriched as listDomainsEnriched, repos_listGroupDnsRecords as listGroupDnsRecords, repos_listGroups as listGroups, repos_listRecordsForBinding as listRecordsForBinding, repos_listServiceGroups as listServiceGroups, repos_listServiceIps as listServiceIps, repos_listServices as listServices, repos_listServicesByGroup as listServicesByGroup, repos_listSubdomainsByDomain as listSubdomainsByDomain, repos_listUngroupedServices as listUngroupedServices, repos_markDnsPendingDelete as markDnsPendingDelete, repos_reorderServices as reorderServices, repos_replaceBindingIps as replaceBindingIps, repos_replaceServiceIps as replaceServiceIps, repos_setBindingCnameTarget as setBindingCnameTarget, repos_setBindingDnsRecordId as setBindingDnsRecordId, repos_setDnsSyncStatus as setDnsSyncStatus, repos_setDomainLastSynced as setDomainLastSynced, repos_setServiceEnabled as setServiceEnabled, repos_setServiceGroup as setServiceGroup, repos_setServiceGroupEnabled as setServiceGroupEnabled, repos_unlinkBindingRecord as unlinkBindingRecord, repos_unlinkGroupDnsRecord as unlinkGroupDnsRecord, repos_updateBindingFields as updateBindingFields, repos_updateDnsFields as updateDnsFields, repos_updateDomain as updateDomain, repos_updateGroup as updateGroup, repos_updateService as updateService, repos_updateServiceGroup as updateServiceGroup, repos_updateSubdomain as updateSubdomain, repos_upsertCertificateCheck as upsertCertificateCheck, repos_upsertSubdomain as upsertSubdomain };
|
||||
}
|
||||
|
||||
export { ConflictError, type Db, type DnsListFilter, NotFoundError, type Sqlite, certificates, createDb, createMemoryDb, dnsRecords, domains, groups, healthCheck, repos, resolveDatabasePath, runMigrations, schema, serviceBindingIps, serviceBindingRecords, serviceBindings, serviceGroupDnsRecords, serviceGroups, serviceIps, services, subdomains, syncJobs };
|
||||
export { ConflictError, type Db, type DnsListFilter, NotFoundError, type Sqlite, type UpdateSubdomainPatch, certificates, createDb, createMemoryDb, dnsRecords, domains, groups, healthCheck, repos, resolveDatabasePath, runMigrations, schema, serviceBindingIps, serviceBindingRecords, serviceBindings, serviceGroupDnsRecords, serviceGroups, serviceIps, services, subdomains, syncJobs };
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE subdomains ADD COLUMN enabled INTEGER NOT NULL DEFAULT 1;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE service_bindings ADD COLUMN cname_target TEXT;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE services ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0;
|
||||
UPDATE services SET sort_order = id;
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE domains ADD COLUMN cert_monitoring TEXT NOT NULL DEFAULT 'auto'
|
||||
CHECK (cert_monitoring IN ('auto', 'required', 'skipped'));
|
||||
|
||||
ALTER TABLE subdomains ADD COLUMN cert_monitoring TEXT NOT NULL DEFAULT 'auto'
|
||||
CHECK (cert_monitoring IN ('auto', 'required', 'skipped'));
|
||||
@@ -2,4 +2,4 @@ export * from "./schema.js";
|
||||
export * from "./client.js";
|
||||
export * from "./errors.js";
|
||||
export * as repos from "./repos.js";
|
||||
export type { DnsListFilter } from "./repos.js";
|
||||
export type { DnsListFilter, UpdateSubdomainPatch } from "./repos.js";
|
||||
|
||||
+209
-20
@@ -12,7 +12,8 @@ import type {
|
||||
Subdomain,
|
||||
SyncJob,
|
||||
} from "@cfdm/shared";
|
||||
import { and, asc, count, eq, isNull, like, or, sql } from "drizzle-orm";
|
||||
import { dnsRecordNamesMatch } from "@cfdm/shared";
|
||||
import { and, asc, count, eq, isNull, like, notInArray, or, sql } from "drizzle-orm";
|
||||
import type { Db } from "./client.js";
|
||||
import { NotFoundError } from "./errors.js";
|
||||
import {
|
||||
@@ -158,14 +159,24 @@ export function updateDomain(
|
||||
id: number,
|
||||
groupId: number | null,
|
||||
status: string,
|
||||
certMonitoring?: string,
|
||||
): Domain {
|
||||
const updates: {
|
||||
group_id: number | null;
|
||||
status: string;
|
||||
cert_monitoring?: string;
|
||||
updated_at: ReturnType<typeof sql>;
|
||||
} = {
|
||||
group_id: groupId,
|
||||
status,
|
||||
updated_at: sql`datetime('now')`,
|
||||
};
|
||||
if (certMonitoring !== undefined) {
|
||||
updates.cert_monitoring = certMonitoring;
|
||||
}
|
||||
const result = db
|
||||
.update(domains)
|
||||
.set({
|
||||
group_id: groupId,
|
||||
status,
|
||||
updated_at: sql`datetime('now')`,
|
||||
})
|
||||
.set(updates)
|
||||
.where(eq(domains.id, id))
|
||||
.run();
|
||||
if (result.changes === 0) throw new NotFoundError(`domain ${id}`);
|
||||
@@ -208,6 +219,19 @@ export function getSubdomain(db: Db, id: number): Subdomain {
|
||||
return row as Subdomain;
|
||||
}
|
||||
|
||||
export function findSubdomainByDomainAndName(
|
||||
db: Db,
|
||||
domainId: number,
|
||||
name: string,
|
||||
): Subdomain | null {
|
||||
const row = db
|
||||
.select()
|
||||
.from(subdomains)
|
||||
.where(and(eq(subdomains.domain_id, domainId), eq(subdomains.name, name)))
|
||||
.get();
|
||||
return (row as Subdomain) ?? null;
|
||||
}
|
||||
|
||||
export function upsertSubdomain(
|
||||
db: Db,
|
||||
domainId: number,
|
||||
@@ -237,15 +261,36 @@ export function createSubdomain(
|
||||
return getSubdomain(db, id);
|
||||
}
|
||||
|
||||
export interface UpdateSubdomainPatch {
|
||||
name?: string;
|
||||
fqdn?: string;
|
||||
enabled?: boolean;
|
||||
cert_monitoring?: string;
|
||||
}
|
||||
|
||||
export function updateSubdomain(
|
||||
db: Db,
|
||||
id: number,
|
||||
name: string,
|
||||
fqdn: string,
|
||||
patch: UpdateSubdomainPatch,
|
||||
): Subdomain {
|
||||
const updates: {
|
||||
name?: string;
|
||||
fqdn?: string;
|
||||
enabled?: boolean;
|
||||
cert_monitoring?: string;
|
||||
updated_at: ReturnType<typeof sql>;
|
||||
} = { updated_at: sql`datetime('now')` };
|
||||
|
||||
if (patch.name !== undefined) updates.name = patch.name;
|
||||
if (patch.fqdn !== undefined) updates.fqdn = patch.fqdn;
|
||||
if (patch.enabled !== undefined) updates.enabled = patch.enabled;
|
||||
if (patch.cert_monitoring !== undefined) {
|
||||
updates.cert_monitoring = patch.cert_monitoring;
|
||||
}
|
||||
|
||||
const result = db
|
||||
.update(subdomains)
|
||||
.set({ name, fqdn, updated_at: sql`datetime('now')` })
|
||||
.set(updates)
|
||||
.where(eq(subdomains.id, id))
|
||||
.run();
|
||||
if (result.changes === 0) throw new NotFoundError(`subdomain ${id}`);
|
||||
@@ -448,8 +493,25 @@ export function markDnsPendingDelete(db: Db, id: number): void {
|
||||
|
||||
// --- Services ---
|
||||
|
||||
function maxSortOrderInGroup(db: Db, groupId: number | null): number {
|
||||
const condition =
|
||||
groupId === null
|
||||
? isNull(services.service_group_id)
|
||||
: eq(services.service_group_id, groupId);
|
||||
const row = db
|
||||
.select({ maxOrder: sql<number>`coalesce(max(${services.sort_order}), -1)` })
|
||||
.from(services)
|
||||
.where(condition)
|
||||
.get();
|
||||
return row?.maxOrder ?? -1;
|
||||
}
|
||||
|
||||
export function listServices(db: Db): Service[] {
|
||||
return db.select().from(services).orderBy(asc(services.name)).all() as Service[];
|
||||
return db
|
||||
.select()
|
||||
.from(services)
|
||||
.orderBy(asc(services.sort_order), asc(services.name))
|
||||
.all() as Service[];
|
||||
}
|
||||
|
||||
export function listServicesByGroup(db: Db, groupId: number): Service[] {
|
||||
@@ -457,7 +519,7 @@ export function listServicesByGroup(db: Db, groupId: number): Service[] {
|
||||
.select()
|
||||
.from(services)
|
||||
.where(eq(services.service_group_id, groupId))
|
||||
.orderBy(asc(services.name))
|
||||
.orderBy(asc(services.sort_order), asc(services.name))
|
||||
.all() as Service[];
|
||||
}
|
||||
|
||||
@@ -466,7 +528,7 @@ export function listUngroupedServices(db: Db): Service[] {
|
||||
.select()
|
||||
.from(services)
|
||||
.where(isNull(services.service_group_id))
|
||||
.orderBy(asc(services.name))
|
||||
.orderBy(asc(services.sort_order), asc(services.name))
|
||||
.all() as Service[];
|
||||
}
|
||||
|
||||
@@ -477,9 +539,10 @@ export function getService(db: Db, id: number): Service {
|
||||
}
|
||||
|
||||
export function createService(db: Db, name: string, slug: string): Service {
|
||||
const sortOrder = maxSortOrderInGroup(db, null) + 1;
|
||||
const id = db
|
||||
.insert(services)
|
||||
.values({ name, slug, subdomain: slug })
|
||||
.values({ name, slug, subdomain: slug, sort_order: sortOrder })
|
||||
.returning({ id: services.id })
|
||||
.get()!.id;
|
||||
return getService(db, id);
|
||||
@@ -520,12 +583,56 @@ export function setServiceGroup(
|
||||
id: number,
|
||||
groupId: number | null,
|
||||
): void {
|
||||
const sortOrder = maxSortOrderInGroup(db, groupId) + 1;
|
||||
db.update(services)
|
||||
.set({ service_group_id: groupId, updated_at: sql`datetime('now')` })
|
||||
.set({
|
||||
service_group_id: groupId,
|
||||
sort_order: sortOrder,
|
||||
updated_at: sql`datetime('now')`,
|
||||
})
|
||||
.where(eq(services.id, id))
|
||||
.run();
|
||||
}
|
||||
|
||||
export function reorderServices(
|
||||
db: Db,
|
||||
groupId: number | null,
|
||||
orderedIds: number[],
|
||||
): void {
|
||||
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: sql`datetime('now')` })
|
||||
.where(eq(services.id, orderedIds[index]!))
|
||||
.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteService(db: Db, id: number): void {
|
||||
const result = db.delete(services).where(eq(services.id, id)).run();
|
||||
if (result.changes === 0) throw new NotFoundError(`service ${id}`);
|
||||
@@ -668,6 +775,20 @@ export function replaceBindingIps(
|
||||
}
|
||||
}
|
||||
|
||||
export function setBindingCnameTarget(
|
||||
db: Db,
|
||||
bindingId: number,
|
||||
target: string | null,
|
||||
): void {
|
||||
db.update(serviceBindings)
|
||||
.set({
|
||||
cname_target: target,
|
||||
updated_at: sql`datetime('now')`,
|
||||
})
|
||||
.where(eq(serviceBindings.id, bindingId))
|
||||
.run();
|
||||
}
|
||||
|
||||
// --- Service Binding Records ---
|
||||
|
||||
export function listRecordsForBinding(db: Db, bindingId: number): DnsRecord[] {
|
||||
@@ -744,8 +865,61 @@ export function unlinkGroupDnsRecord(
|
||||
|
||||
// --- Service Bindings ---
|
||||
|
||||
function dnsRecordMatchesHostname(
|
||||
recordName: string,
|
||||
hostname: string,
|
||||
zoneName: string,
|
||||
): boolean {
|
||||
return dnsRecordNamesMatch(recordName, hostname, zoneName);
|
||||
}
|
||||
|
||||
function enrichServiceBindingView(
|
||||
db: Db,
|
||||
row: Omit<ServiceBindingView, "target_ips"> & { target_ips?: string[] },
|
||||
): ServiceBindingView {
|
||||
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 = [
|
||||
...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,
|
||||
};
|
||||
}
|
||||
|
||||
export function listAllBindings(db: Db): ServiceBindingView[] {
|
||||
return db.all<ServiceBindingView>(sql`
|
||||
return db
|
||||
.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
@@ -757,11 +931,13 @@ export function listAllBindings(db: Db): ServiceBindingView[] {
|
||||
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));
|
||||
}
|
||||
|
||||
export function listBindingsByDomain(db: Db, domainId: number): ServiceBindingView[] {
|
||||
return db.all<ServiceBindingView>(sql`
|
||||
return db
|
||||
.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
@@ -774,7 +950,8 @@ export function listBindingsByDomain(db: Db, domainId: number): ServiceBindingVi
|
||||
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));
|
||||
}
|
||||
|
||||
export function listBindingsByService(db: Db, serviceId: number): Array<ServiceBinding & { zone_name: string }> {
|
||||
@@ -796,7 +973,7 @@ export function getBinding(db: Db, id: number): ServiceBinding {
|
||||
}
|
||||
|
||||
export function getBindingView(db: Db, id: number): ServiceBindingView {
|
||||
const rows = db.all<ServiceBindingView>(sql`
|
||||
const rows = db.all<Omit<ServiceBindingView, "target_ips">>(sql`
|
||||
SELECT sb.id, sb.domain_id, sb.service_id, sb.hostname, sb.dns_record_id,
|
||||
d.zone_name, d.group_id, g.name AS group_name,
|
||||
s.name AS service_name, s.slug AS service_slug,
|
||||
@@ -810,7 +987,7 @@ export function getBindingView(db: Db, id: number): ServiceBindingView {
|
||||
WHERE sb.id = ${id}
|
||||
`);
|
||||
if (!rows[0]) throw new NotFoundError(`service binding ${id}`);
|
||||
return rows[0];
|
||||
return enrichServiceBindingView(db, rows[0]);
|
||||
}
|
||||
|
||||
export function findBinding(
|
||||
@@ -1005,6 +1182,18 @@ export function countCertificatesByStatus(
|
||||
return rows.map((r) => [r.status, r.cnt]);
|
||||
}
|
||||
|
||||
export function deleteCertificatesNotIn(db: Db, hostnames: string[]): number {
|
||||
if (hostnames.length === 0) {
|
||||
const result = db.delete(certificates).run();
|
||||
return result.changes;
|
||||
}
|
||||
const result = db
|
||||
.delete(certificates)
|
||||
.where(notInArray(certificates.hostname, hostnames))
|
||||
.run();
|
||||
return result.changes;
|
||||
}
|
||||
|
||||
// --- Sync Jobs ---
|
||||
|
||||
export function createSyncJob(
|
||||
|
||||
@@ -28,6 +28,7 @@ export const 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')`),
|
||||
@@ -59,6 +60,7 @@ export const 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()
|
||||
@@ -75,6 +77,8 @@ export const subdomains = sqliteTable("subdomains", {
|
||||
.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')`),
|
||||
@@ -115,6 +119,7 @@ export const serviceBindings = sqliteTable("service_bindings", {
|
||||
.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",
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user