import { createDb, domain } from "@minmon/db"; import { eq } from "drizzle-orm"; import { resolve4 } from "node:dns/promises"; import { Effect } from "effect"; export type DomainCheckStatus = "MATCH" | "MISMATCH" | "UNRESOLVED"; export type DomainCheckResult = { status: DomainCheckStatus; resolvedIps: string[]; checkedAt: Date; message: string | null; }; type DomainCheckTarget = { name: string; expectedServerIp: string; }; export function resolveIpv4ARecords(hostname: string) { return Effect.tryPromise({ try: async () => [...new Set(await resolve4(hostname))], catch: (error: unknown) => error instanceof Error ? error : new Error("Unable to resolve IPv4 A records"), }); } export function checkDomainDns(target: DomainCheckTarget) { return Effect.match(resolveIpv4ARecords(target.name), { onSuccess: (resolvedIps: string[]): DomainCheckResult => ({ status: resolvedIps.includes(target.expectedServerIp) ? "MATCH" : "MISMATCH", resolvedIps, checkedAt: new Date(), message: null, }), onFailure: (error: Error): DomainCheckResult => ({ status: "UNRESOLVED", resolvedIps: [], checkedAt: new Date(), message: error.message, }), }); } export async function persistDomainCheck( database: ReturnType, input: { domainId: string; result: DomainCheckResult; }, ) { const [updatedDomain] = await database .update(domain) .set({ resolutionStatus: input.result.status, lastResolvedIps: input.result.resolvedIps, lastResolvedIp: input.result.resolvedIps[0] ?? null, lastCheckedAt: input.result.checkedAt, lastCheckMessage: input.result.message, }) .where(eq(domain.id, input.domainId)) .returning(); return updatedDomain ?? null; } export async function runAndPersistDomainCheck( database: ReturnType, input: { domainId: string; name: string; expectedServerIp: string; }, ) { const result = await Effect.runPromise( checkDomainDns({ name: input.name, expectedServerIp: input.expectedServerIp, }), ); const updatedDomain = await persistDomainCheck(database, { domainId: input.domainId, result, }); return { result, domain: updatedDomain, }; }