1
mirror of https://github.com/jakejarvis/hoot.git synced 2025-10-18 20:14:25 -04:00
Files
hoot/server/repos/dns.ts

99 lines
2.9 KiB
TypeScript

import "server-only";
import type { InferInsertModel } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
import { db } from "@/server/db/client";
import {
dnsRecords,
type dnsRecordType,
type dnsResolver,
} from "@/server/db/schema";
type DnsRecordInsert = InferInsertModel<typeof dnsRecords>;
export type UpsertDnsParams = {
domainId: string;
resolver: (typeof dnsResolver.enumValues)[number];
fetchedAt: Date;
// complete set per type
recordsByType: Record<
(typeof dnsRecordType.enumValues)[number],
Array<
Omit<
DnsRecordInsert,
"id" | "domainId" | "type" | "resolver" | "fetchedAt"
>
>
>;
};
export async function replaceDns(params: UpsertDnsParams) {
const { domainId, recordsByType } = params;
// For each type, compute replace-set by (type,name,value)
for (const type of Object.keys(recordsByType) as Array<
(typeof dnsRecordType.enumValues)[number]
>) {
const next = (recordsByType[type] ?? []).map((r) => ({
...r,
// Normalize DNS record name/value for case-insensitive uniqueness
name: (r.name as string).trim().toLowerCase(),
value: (r.value as string).trim().toLowerCase(),
}));
const existing = await db
.select({
id: dnsRecords.id,
name: dnsRecords.name,
value: dnsRecords.value,
})
.from(dnsRecords)
.where(and(eq(dnsRecords.domainId, domainId), eq(dnsRecords.type, type)));
const nextKey = (r: (typeof next)[number]) =>
`${type as string}|${r.name as string}|${r.value as string}`;
const nextMap = new Map(next.map((r) => [nextKey(r), r]));
const toDelete = existing
.filter(
(e) =>
!nextMap.has(
`${type}|${e.name.trim().toLowerCase()}|${e.value
.trim()
.toLowerCase()}`,
),
)
.map((e) => e.id);
if (toDelete.length > 0) {
await db.delete(dnsRecords).where(inArray(dnsRecords.id, toDelete));
}
for (const r of next) {
await db
.insert(dnsRecords)
.values({
domainId,
type,
name: r.name as string,
value: r.value as string,
ttl: r.ttl ?? null,
priority: r.priority ?? null,
isCloudflare: r.isCloudflare ?? null,
resolver: params.resolver,
fetchedAt: params.fetchedAt,
expiresAt: r.expiresAt as Date,
})
.onConflictDoUpdate({
target: [
dnsRecords.domainId,
dnsRecords.type,
dnsRecords.name,
dnsRecords.value,
],
set: {
ttl: r.ttl ?? null,
priority: r.priority ?? null,
isCloudflare: r.isCloudflare ?? null,
resolver: params.resolver,
fetchedAt: params.fetchedAt,
expiresAt: r.expiresAt as Date,
},
});
}
}
}