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

Update hosting schema to include country_code and remove emoji field

This commit is contained in:
2025-10-13 10:31:51 -04:00
parent 0b4675ad9c
commit 74ba13f753
7 changed files with 57 additions and 30 deletions

View File

@@ -85,9 +85,9 @@ describe("DomainReportView", () => {
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
},
isError: false,

View File

@@ -26,9 +26,9 @@ describe("HostingEmailSection", () => {
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
} as unknown as import("@/lib/schemas").Hosting;
render(

View File

@@ -9,9 +9,9 @@ export const HostingSchema = z.object({
city: z.string(),
region: z.string(),
country: z.string(),
country_code: z.string(),
lat: z.number().nullable(),
lon: z.number().nullable(),
emoji: z.string().nullable(),
}),
});

View File

@@ -23,9 +23,9 @@ vi.mock("@/server/services/ip", () => ({
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
owner: null,
domain: null,
@@ -69,12 +69,11 @@ describe("detectHosting", () => {
]);
(lookupIpMeta as unknown as Mock).mockResolvedValue({
geo: {
city: "SF",
city: "San Francisco",
region: "CA",
country: "US",
lat: 1,
lon: 2,
emoji: "🇺🇸",
},
owner: null,
domain: null,
@@ -134,9 +133,9 @@ describe("detectHosting", () => {
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
owner: "My ISP",
domain: "isp.example",

View File

@@ -39,9 +39,9 @@ export async function detectHosting(domain: string): Promise<Hosting> {
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
owner: null,
domain: null,

View File

@@ -9,12 +9,12 @@ afterEach(() => {
describe("lookupIpMeta", () => {
it("parses ipwho.is response and derives owner and domain", async () => {
const resp = {
city: "SF",
city: "San Francisco",
region: "CA",
country: "US",
country: "United States",
country_code: "US",
latitude: 37.7,
longitude: -122.4,
flag: { emoji: "🇺🇸" },
connection: {
org: "Cloudflare",
isp: "Cloudflare, Inc",
@@ -25,7 +25,7 @@ describe("lookupIpMeta", () => {
.spyOn(global, "fetch")
.mockResolvedValue(new Response(JSON.stringify(resp), { status: 200 }));
const res = await lookupIpMeta("1.2.3.4");
expect(res.geo.city).toBe("SF");
expect(res.geo.city).toBe("San Francisco");
expect(res.owner).toBe("Cloudflare");
expect(res.domain).toBe("cloudflare.com");
fetchMock.mockRestore();

View File

@@ -3,9 +3,9 @@ export async function lookupIpMeta(ip: string): Promise<{
city: string;
region: string;
country: string;
country_code: string;
lat: number | null;
lon: number | null;
emoji: string | null;
};
owner: string | null;
domain: string | null;
@@ -17,52 +17,80 @@ export async function lookupIpMeta(ip: string): Promise<{
if (!res.ok) throw new Error("ipwho.is fail");
const j = (await res.json()) as {
city?: string;
region?: string;
state?: string;
ip?: string;
success?: boolean;
type?: "IPv4" | "IPv6";
continent?: string;
continent_code?: string;
country?: string;
country_code?: string;
region?: string;
region_code?: string;
city?: string;
latitude?: number;
longitude?: number;
flag?: { emoji?: string };
connection?: { org?: string; isp?: string; domain?: string };
is_eu?: boolean;
postal?: string;
calling_code?: string;
capital?: string;
borders?: string; // e.g., "CA,MX"
flag?: {
img?: string; // URL to SVG/PNG
emoji?: string; // e.g., "🇺🇸"
emoji_unicode?: string; // e.g., "U+1F1FA U+1F1F8"
};
connection: {
asn?: number;
org?: string;
isp?: string;
domain?: string;
};
timezone?: {
id?: string; // IANA TZ, e.g., "America/New_York"
abbr?: string; // e.g., "EDT"
is_dst?: boolean;
offset?: number; // seconds offset from UTC (can be negative)
utc?: string; // e.g., "-04:00"
current_time?: string; // ISO8601 with offset
};
};
console.debug("[ip] ipwho.is result", { ip, json: j });
const geo = {
city: j.city || "",
region: j.region || j.state || "",
country: j.country || "",
lat: typeof j.latitude === "number" ? j.latitude : null,
lon: typeof j.longitude === "number" ? j.longitude : null,
emoji: j.flag?.emoji || null,
};
const org = j.connection?.org?.trim();
const isp = j.connection?.isp?.trim();
const owner = (org || isp || "").trim() || null;
const domain = (j.connection?.domain || "").trim() || null;
const geo = {
city: j.city || "",
region: j.region || "",
country: j.country || "",
country_code: j.country_code || "",
lat: typeof j.latitude === "number" ? j.latitude : null,
lon: typeof j.longitude === "number" ? j.longitude : null,
};
console.info("[ip] ok", {
ip,
owner: owner || undefined,
domain: domain || undefined,
country: geo.country,
geo: geo || undefined,
duration_ms: Date.now() - startedAt,
});
return { geo, owner, domain };
} catch {
console.warn("[ip] error", { ip });
return {
owner: null,
domain: null,
geo: {
city: "",
region: "",
country: "",
country_code: "",
lat: null,
lon: null,
emoji: null,
},
owner: null,
domain: null,
};
}
}