minmon/packages/api/src/lib/domain-check.ts

91 lines
2.3 KiB
TypeScript

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<typeof createDb>,
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<typeof createDb>,
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,
};
}