Skip to content

Commit 5e3fa8c

Browse files
committed
refactor: remove redundant domain normalization logic from services, relying on router schema for registrable domains
1 parent 1d43c2c commit 5e3fa8c

File tree

16 files changed

+128
-268
lines changed

16 files changed

+128
-268
lines changed

server/routers/domain.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TRPCError } from "@trpc/server";
12
import z from "zod";
23
import { normalizeDomainInput } from "@/lib/domain";
34
import { toRegistrableDomain } from "@/lib/domain-server";
@@ -28,10 +29,16 @@ import {
2829

2930
const DomainInputSchema = z
3031
.object({ domain: z.string().min(1) })
31-
.transform(({ domain }) => ({ domain: normalizeDomainInput(domain) }))
32-
.refine(({ domain }) => toRegistrableDomain(domain) !== null, {
33-
message: "Invalid domain",
34-
path: ["domain"],
32+
.transform(({ domain }) => {
33+
const normalized = normalizeDomainInput(domain);
34+
const registrable = toRegistrableDomain(normalized);
35+
if (!registrable) {
36+
throw new TRPCError({
37+
code: "BAD_REQUEST",
38+
message: '"domain" must be a valid and registrable',
39+
});
40+
}
41+
return { domain: registrable };
3542
});
3643

3744
export const domainRouter = createTRPCRouter({

server/services/certificates.test.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,6 @@ vi.mock("node:tls", async () => {
2626
};
2727
});
2828

29-
// Mock toRegistrableDomain to allow .invalid domains for testing
30-
vi.mock("@/lib/domain-server", async () => {
31-
const actual = await vi.importActual<typeof import("@/lib/domain-server")>(
32-
"@/lib/domain-server",
33-
);
34-
return {
35-
...actual,
36-
toRegistrableDomain: (input: string) => {
37-
// Allow .invalid domains (reserved, never resolve) for safe testing
38-
if (input.endsWith(".invalid")) {
39-
return input.toLowerCase();
40-
}
41-
// Use real implementation for everything else
42-
return actual.toRegistrableDomain(input);
43-
},
44-
};
45-
});
46-
4729
import {
4830
afterAll,
4931
afterEach,

server/services/certificates.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
makeProviderKey,
1010
} from "@/lib/db/repos/providers";
1111
import { certificates as certTable } from "@/lib/db/schema";
12-
import { toRegistrableDomain } from "@/lib/domain-server";
1312
import { createLogger } from "@/lib/logger/server";
1413
import { detectCertificateAuthority } from "@/lib/providers/detection";
1514
import { scheduleRevalidation } from "@/lib/schedule";
@@ -19,20 +18,15 @@ import { ttlForCertificates } from "@/lib/ttl";
1918
const logger = createLogger({ source: "certificates" });
2019

2120
export async function getCertificates(domain: string): Promise<Certificate[]> {
21+
// Input domain is already normalized to registrable domain by router schema
2222
logger.debug("start", { domain });
2323

24-
// Only support registrable domains (no subdomains, IPs, or invalid TLDs)
25-
const registrable = toRegistrableDomain(domain);
26-
if (!registrable) {
27-
throw new Error(`Cannot extract registrable domain from ${domain}`);
28-
}
29-
3024
// Generate single timestamp for access tracking and scheduling
3125
const now = new Date();
3226
const nowMs = now.getTime();
3327

3428
// Fast path: Check Postgres for cached certificate data
35-
const existingDomain = await findDomainByName(registrable);
29+
const existingDomain = await findDomainByName(domain);
3630
const existing = existingDomain
3731
? await db
3832
.select({
@@ -68,7 +62,7 @@ export async function getCertificates(domain: string): Promise<Certificate[]> {
6862
}));
6963

7064
logger.info("cache hit", {
71-
domain: registrable,
65+
domain,
7266
count: out.length,
7367
cached: true,
7468
});
@@ -177,25 +171,25 @@ export async function getCertificates(domain: string): Promise<Certificate[]> {
177171
after(() => {
178172
const dueAtMs = nextDue.getTime();
179173
scheduleRevalidation(
180-
registrable,
174+
domain,
181175
"certificates",
182176
dueAtMs,
183177
existingDomain.lastAccessedAt ?? null,
184178
).catch((err) => {
185179
logger.error("schedule failed", err, {
186-
domain: registrable,
180+
domain,
187181
});
188182
});
189183
});
190184
}
191185

192186
logger.info("done", {
193-
domain: registrable,
187+
domain,
194188
chainLength: out.length,
195189
});
196190
return out;
197191
} catch (err) {
198-
logger.error("probe failed", err, { domain: registrable });
192+
logger.error("probe failed", err, { domain });
199193
// Do not treat as fatal; return empty and avoid long-lived negative cache
200194
return [];
201195
}

server/services/dns.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { db } from "@/lib/db/client";
77
import { replaceDns } from "@/lib/db/repos/dns";
88
import { findDomainByName } from "@/lib/db/repos/domains";
99
import { dnsRecords } from "@/lib/db/schema";
10-
import { toRegistrableDomain } from "@/lib/domain-server";
1110
import { fetchWithTimeoutAndRetry } from "@/lib/fetch";
1211
import { simpleHash } from "@/lib/hash";
1312
import { createLogger } from "@/lib/logger/server";
@@ -84,25 +83,20 @@ function buildDohUrl(
8483
export const getDnsRecords = cache(async function getDnsRecords(
8584
domain: string,
8685
): Promise<DnsRecordsResponse> {
86+
// Input domain is already normalized to registrable domain by router schema
8787
logger.debug("start", { domain });
8888

8989
const providers = providerOrderForLookup(domain);
9090
const durationByProvider: Record<string, number> = {};
9191
let lastError: unknown = null;
9292
const types = DnsTypeSchema.options;
9393

94-
// Only support registrable domains (no subdomains, IPs, or invalid TLDs)
95-
const registrable = toRegistrableDomain(domain);
96-
if (!registrable) {
97-
throw new Error(`Cannot extract registrable domain from ${domain}`);
98-
}
99-
10094
// Generate single timestamp for access tracking and scheduling
10195
const now = new Date();
10296
const nowMs = now.getTime();
10397

10498
// Fast path: Check Postgres for cached DNS records
105-
const existingDomain = await findDomainByName(registrable);
99+
const existingDomain = await findDomainByName(domain);
106100
const rows = (
107101
existingDomain
108102
? await db
@@ -171,7 +165,7 @@ export const getDnsRecords = cache(async function getDnsRecords(
171165
const sorted = sortDnsRecordsByType(deduplicated, types);
172166
if (allFreshAcrossTypes) {
173167
logger.info("cache hit", {
174-
domain: registrable,
168+
domain,
175169
types: freshTypes.join(","),
176170
cached: true,
177171
});
@@ -244,13 +238,13 @@ export const getDnsRecords = cache(async function getDnsRecords(
244238
// Always schedule: use the soonest expiry if available, otherwise schedule immediately
245239
const soonest = times.length > 0 ? Math.min(...times) : Date.now();
246240
scheduleRevalidation(
247-
registrable,
241+
domain,
248242
"dns",
249243
soonest,
250244
existingDomain.lastAccessedAt ?? null,
251245
).catch((err) => {
252246
logger.error("schedule failed partial", err, {
253-
domain: registrable,
247+
domain,
254248
type: "partial",
255249
});
256250
});
@@ -285,7 +279,7 @@ export const getDnsRecords = cache(async function getDnsRecords(
285279
);
286280

287281
logger.info("partial refresh done", {
288-
domain: registrable,
282+
domain,
289283
counts,
290284
resolver: pinnedProvider.key,
291285
durationMs: durationByProvider[pinnedProvider.key],
@@ -297,7 +291,7 @@ export const getDnsRecords = cache(async function getDnsRecords(
297291
} catch (err) {
298292
// Fall through to full provider loop below
299293
logger.error("partial refresh failed", err, {
300-
domain: registrable,
294+
domain,
301295
provider: pinnedProvider.key,
302296
});
303297
}
@@ -379,20 +373,20 @@ export const getDnsRecords = cache(async function getDnsRecords(
379373
);
380374
const soonest = times.length > 0 ? Math.min(...times) : now.getTime();
381375
scheduleRevalidation(
382-
registrable,
376+
domain,
383377
"dns",
384378
soonest,
385379
existingDomain.lastAccessedAt ?? null,
386380
).catch((err) => {
387381
logger.error("schedule failed full", err, {
388-
domain: registrable,
382+
domain,
389383
type: "full",
390384
});
391385
});
392386
});
393387
}
394388
logger.info("done", {
395-
domain: registrable,
389+
domain,
396390
counts,
397391
resolver: resolverUsed,
398392
durationByProvider,
@@ -404,7 +398,7 @@ export const getDnsRecords = cache(async function getDnsRecords(
404398
return { records: sorted, resolver: resolverUsed } as DnsRecordsResponse;
405399
} catch (err) {
406400
logger.warn("provider attempt failed", {
407-
domain: registrable,
401+
domain,
408402
provider: provider.key,
409403
});
410404
durationByProvider[provider.key] = Date.now() - attemptStart;
@@ -415,10 +409,10 @@ export const getDnsRecords = cache(async function getDnsRecords(
415409

416410
// All providers failed
417411
const error = new Error(
418-
`All DoH providers failed for ${registrable}: ${String(lastError)}`,
412+
`All DoH providers failed for ${domain}: ${String(lastError)}`,
419413
);
420414
logger.error("all providers failed", error, {
421-
domain: registrable,
415+
domain,
422416
providers: providers.map((p) => p.key).join(","),
423417
});
424418
throw error;

server/services/favicon.test.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,6 @@ import {
1010
} from "vitest";
1111
import { RemoteAssetError } from "@/lib/fetch-remote-asset";
1212

13-
// Mock toRegistrableDomain to allow .invalid and .example domains for testing
14-
vi.mock("@/lib/domain-server", async () => {
15-
const actual = await vi.importActual<typeof import("@/lib/domain-server")>(
16-
"@/lib/domain-server",
17-
);
18-
return {
19-
...actual,
20-
toRegistrableDomain: (input: string) => {
21-
// Allow .invalid and .example domains (reserved, never resolve) for safe testing
22-
if (input.endsWith(".invalid") || input.endsWith(".example")) {
23-
return input.toLowerCase();
24-
}
25-
// Use real implementation for everything else
26-
return actual.toRegistrableDomain(input);
27-
},
28-
};
29-
});
30-
3113
const storageMock = vi.hoisted(() => ({
3214
storeImage: vi.fn(async () => ({
3315
url: "https://test-store.public.blob.vercel-storage.com/abcdef0123456789abcdef0123456789/32x32.webp",

0 commit comments

Comments
 (0)