import { db } from "@/lib/db"; import { geoCache } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; interface GeoLocation { latitude: number; longitude: number; city: string; country: string; countryCode: string; region: string; } interface IpApiResponse { status: string; lat: number; lon: number; city: string; country: string; countryCode: string; } const CONTINENT_MAP: Record = { // Asia CN: "Asia", JP: "Asia", KR: "Asia", IN: "Asia", ID: "Asia", TH: "Asia", VN: "Asia", PH: "Asia", MY: "Asia", SG: "Asia", TW: "Asia", HK: "Asia", BD: "Asia", PK: "Asia", LK: "Asia", NP: "Asia", MM: "Asia", KH: "Asia", LA: "Asia", MN: "Asia", KZ: "Asia", UZ: "Asia", AE: "Asia", SA: "Asia", IL: "Asia", TR: "Asia", IQ: "Asia", IR: "Asia", QA: "Asia", KW: "Asia", BH: "Asia", OM: "Asia", JO: "Asia", LB: "Asia", AF: "Asia", // Europe GB: "Europe", DE: "Europe", FR: "Europe", IT: "Europe", ES: "Europe", PT: "Europe", NL: "Europe", BE: "Europe", SE: "Europe", NO: "Europe", DK: "Europe", FI: "Europe", PL: "Europe", CZ: "Europe", AT: "Europe", CH: "Europe", IE: "Europe", RO: "Europe", HU: "Europe", GR: "Europe", UA: "Europe", RU: "Europe", BG: "Europe", HR: "Europe", SK: "Europe", SI: "Europe", RS: "Europe", LT: "Europe", LV: "Europe", EE: "Europe", IS: "Europe", LU: "Europe", MT: "Europe", CY: "Europe", AL: "Europe", MK: "Europe", BA: "Europe", ME: "Europe", MD: "Europe", BY: "Europe", // Americas US: "Americas", CA: "Americas", MX: "Americas", BR: "Americas", AR: "Americas", CO: "Americas", CL: "Americas", PE: "Americas", VE: "Americas", EC: "Americas", BO: "Americas", PY: "Americas", UY: "Americas", CR: "Americas", PA: "Americas", CU: "Americas", DO: "Americas", GT: "Americas", HN: "Americas", SV: "Americas", NI: "Americas", JM: "Americas", TT: "Americas", HT: "Americas", PR: "Americas", GY: "Americas", SR: "Americas", BZ: "Americas", // Africa ZA: "Africa", NG: "Africa", KE: "Africa", EG: "Africa", GH: "Africa", ET: "Africa", TZ: "Africa", MA: "Africa", DZ: "Africa", TN: "Africa", UG: "Africa", SN: "Africa", CI: "Africa", CM: "Africa", MZ: "Africa", MG: "Africa", AO: "Africa", ZW: "Africa", RW: "Africa", LY: "Africa", SD: "Africa", CD: "Africa", ML: "Africa", BF: "Africa", NE: "Africa", MW: "Africa", ZM: "Africa", BW: "Africa", NA: "Africa", MU: "Africa", // Oceania AU: "Oceania", NZ: "Oceania", FJ: "Oceania", PG: "Oceania", WS: "Oceania", TO: "Oceania", VU: "Oceania", SB: "Oceania", GU: "Oceania", NC: "Oceania", PF: "Oceania", }; function getRegion(countryCode: string): string { return CONTINENT_MAP[countryCode] ?? "Unknown"; } export async function getGeoLocation( ip: string ): Promise { try { const cached = await db .select() .from(geoCache) .where(eq(geoCache.ip, ip)) .limit(1); if (cached.length > 0) { const row = cached[0]; return { latitude: Number(row.latitude), longitude: Number(row.longitude), city: row.city ?? "", country: row.country ?? "", countryCode: row.countryCode ?? "", region: row.region ?? "", }; } const response = await fetch(`http://ip-api.com/json/${ip}`, { signal: AbortSignal.timeout(5000), }); if (!response.ok) { return null; } const data: IpApiResponse = await response.json(); if (data.status !== "success") { return null; } const region = getRegion(data.countryCode); const geoData: GeoLocation = { latitude: data.lat, longitude: data.lon, city: data.city, country: data.country, countryCode: data.countryCode, region, }; await db.insert(geoCache).values({ ip, latitude: String(geoData.latitude), longitude: String(geoData.longitude), city: geoData.city, country: geoData.country, countryCode: geoData.countryCode, region: geoData.region, }); return geoData; } catch (error) { console.error("Failed to get geo location for IP:", ip, error); return null; } }