diff --git a/apps/api/src/controllers/event.controller.ts b/apps/api/src/controllers/event.controller.ts index 24e9eb1e..cd12497e 100644 --- a/apps/api/src/controllers/event.controller.ts +++ b/apps/api/src/controllers/event.controller.ts @@ -1,4 +1,4 @@ -import { getClientIp, parseIp } from '@/utils/parseIp'; +import { getClientIp, parseIp } from '@/utils/parse-ip'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { generateDeviceId } from '@openpanel/common/server'; diff --git a/apps/api/src/controllers/profile.controller.ts b/apps/api/src/controllers/profile.controller.ts index cfb2aef7..b58d6df6 100644 --- a/apps/api/src/controllers/profile.controller.ts +++ b/apps/api/src/controllers/profile.controller.ts @@ -1,4 +1,4 @@ -import { getClientIp, parseIp } from '@/utils/parseIp'; +import { getClientIp, parseIp } from '@/utils/parse-ip'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { assocPath, pathOr } from 'ramda'; diff --git a/apps/api/src/controllers/track.controller.ts b/apps/api/src/controllers/track.controller.ts index 3b5acad1..f043d2ed 100644 --- a/apps/api/src/controllers/track.controller.ts +++ b/apps/api/src/controllers/track.controller.ts @@ -1,5 +1,5 @@ -import type { GeoLocation } from '@/utils/parseIp'; -import { getClientIp, parseIp } from '@/utils/parseIp'; +import type { GeoLocation } from '@/utils/parse-ip'; +import { getClientIp, parseIp } from '@/utils/parse-ip'; import type { FastifyReply, FastifyRequest } from 'fastify'; import { path, assocPath, pathOr, pick } from 'ramda'; diff --git a/apps/api/src/hooks/ip.hook.ts b/apps/api/src/hooks/ip.hook.ts index 4493098f..feb01449 100644 --- a/apps/api/src/hooks/ip.hook.ts +++ b/apps/api/src/hooks/ip.hook.ts @@ -1,4 +1,4 @@ -import { getClientIp } from '@/utils/parseIp'; +import { getClientIp } from '@/utils/parse-ip'; import type { FastifyReply, FastifyRequest, diff --git a/apps/api/src/utils/parseIp.ts b/apps/api/src/utils/parse-ip.ts similarity index 70% rename from apps/api/src/utils/parseIp.ts rename to apps/api/src/utils/parse-ip.ts index 81e2e6e4..2f97f44f 100644 --- a/apps/api/src/utils/parseIp.ts +++ b/apps/api/src/utils/parse-ip.ts @@ -1,6 +1,7 @@ +import crypto from 'node:crypto'; +import { getRedisCache } from '@openpanel/redis'; import type { FastifyRequest } from 'fastify'; import requestIp from 'request-ip'; - import { logger } from './logger'; interface RemoteIpLookupResponse { @@ -19,7 +20,7 @@ export interface GeoLocation { latitude: number | undefined; } -const geo: GeoLocation = { +const DEFAULT_GEO: GeoLocation = { country: undefined, city: undefined, region: undefined, @@ -35,29 +36,45 @@ export function getClientIp(req: FastifyRequest) { export async function parseIp(ip?: string): Promise { if (!ip || ignore.includes(ip)) { - return geo; + return DEFAULT_GEO; + } + + const hash = crypto.createHash('sha256').update(ip).digest('hex'); + const cached = await getRedisCache().get(`geo:${hash}`); + + if (cached) { + return JSON.parse(cached); } try { const res = await fetch(`${process.env.GEO_IP_HOST}/${ip}`, { - signal: AbortSignal.timeout(2000), + signal: AbortSignal.timeout(4000), }); if (!res.ok) { - return geo; + return DEFAULT_GEO; } const json = (await res.json()) as RemoteIpLookupResponse; - return { + const geo = { country: json.country, city: json.city, region: json.stateprov, longitude: json.longitude, latitude: json.latitude, }; + + await getRedisCache().set( + `geo:${hash}`, + JSON.stringify(geo), + 'EX', + 60 * 30, + ); + + return geo; } catch (error) { logger.error('Failed to fetch geo location for ip', { error }); - return geo; + return DEFAULT_GEO; } }