[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build an IP Geolocation Service

Build an IP Geolocation Service

Build an IP geolocation service with MaxMind GeoIP2 database, Redis caching, country/city/ISP resolution, geo-based access rules, and compliance with regional data laws.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Leon leads engineering at a 25-person fintech serving 15 countries. They need IP geolocation for three things: showing the right currency/language automatically, blocking transactions from sanctioned countries (OFAC compliance), and detecting VPN/proxy usage for fraud prevention. They used a third-party API at $0.001/request — at 50M requests/month, that's $50K/year. Plus, external API calls add 100-200ms latency to every request. They need a local geolocation solution: fast, accurate, and compliant.

Step 1: Build the Geolocation Service

typescript
// src/geo/ip-lookup.ts — IP geolocation with MaxMind, caching, and compliance rules
import { Reader, CityResponse, AsnResponse } from "maxmind";
import { Redis } from "ioredis";
import { pool } from "../db";
import path from "node:path";

const redis = new Redis(process.env.REDIS_URL!);

let cityReader: Reader<CityResponse>;
let asnReader: Reader<AsnResponse>;

// Initialize MaxMind databases (downloaded via geoipupdate)
export async function initGeoIP(): Promise<void> {
  const { open } = await import("maxmind");
  const dbPath = process.env.GEOIP_DB_PATH || "/usr/share/GeoIP";

  cityReader = await open<CityResponse>(path.join(dbPath, "GeoLite2-City.mmdb"));
  asnReader = await open<AsnResponse>(path.join(dbPath, "GeoLite2-ASN.mmdb"));
}

interface GeoResult {
  ip: string;
  country: { code: string; name: string } | null;
  city: string | null;
  region: string | null;
  postalCode: string | null;
  location: { latitude: number; longitude: number; accuracyRadius: number } | null;
  timezone: string | null;
  isp: { name: string; asn: number } | null;
  flags: {
    isProxy: boolean;
    isVPN: boolean;
    isTor: boolean;
    isHosting: boolean;
    isSanctioned: boolean;
  };
  currency: string | null;
  language: string | null;
}

// OFAC sanctioned countries
const SANCTIONED_COUNTRIES = new Set([
  "CU", "IR", "KP", "SY", "RU",  // simplified list
]);

// Known hosting/VPN ASNs (partial list, update regularly)
const HOSTING_ASNS = new Set([
  14061,   // DigitalOcean
  16509,   // Amazon AWS
  15169,   // Google Cloud
  8075,    // Microsoft Azure
  13335,   // Cloudflare
  20473,   // Vultr
  63949,   // Linode
  24940,   // Hetzner
]);

// Country → default currency
const COUNTRY_CURRENCY: Record<string, string> = {
  US: "USD", GB: "GBP", DE: "EUR", FR: "EUR", JP: "JPY", CA: "CAD",
  AU: "AUD", CH: "CHF", SE: "SEK", IN: "INR", BR: "BRL", MX: "MXN",
  KR: "KRW", SG: "SGD", HK: "HKD", NZ: "NZD", NO: "NOK", DK: "DKK",
  PL: "PLN", CZ: "CZK", SK: "EUR", AT: "EUR", NL: "EUR", BE: "EUR",
};

// Country → default language
const COUNTRY_LANGUAGE: Record<string, string> = {
  US: "en", GB: "en", DE: "de", FR: "fr", JP: "ja", ES: "es",
  IT: "it", PT: "pt", NL: "nl", KR: "ko", CN: "zh", RU: "ru",
  PL: "pl", CZ: "cs", SK: "sk", SE: "sv", NO: "no", DK: "da",
};

// Lookup IP with caching
export async function lookupIP(ip: string): Promise<GeoResult> {
  // Check cache (IPs don't change location often)
  const cached = await redis.get(`geo:${ip}`);
  if (cached) return JSON.parse(cached);

  const result = performLookup(ip);

  // Cache for 24 hours
  await redis.setex(`geo:${ip}`, 86400, JSON.stringify(result));

  return result;
}

function performLookup(ip: string): GeoResult {
  const cityData = cityReader.get(ip);
  const asnData = asnReader.get(ip);

  const countryCode = cityData?.country?.iso_code || null;
  const asn = asnData?.autonomous_system_number || 0;

  return {
    ip,
    country: countryCode
      ? { code: countryCode, name: cityData?.country?.names?.en || countryCode }
      : null,
    city: cityData?.city?.names?.en || null,
    region: cityData?.subdivisions?.[0]?.names?.en || null,
    postalCode: cityData?.postal?.code || null,
    location: cityData?.location
      ? {
          latitude: cityData.location.latitude!,
          longitude: cityData.location.longitude!,
          accuracyRadius: cityData.location.accuracy_radius || 100,
        }
      : null,
    timezone: cityData?.location?.time_zone || null,
    isp: asnData
      ? { name: asnData.autonomous_system_organization || "Unknown", asn }
      : null,
    flags: {
      isProxy: cityData?.traits?.is_anonymous_proxy || false,
      isVPN: cityData?.traits?.is_anonymous_vpn || false,
      isTor: cityData?.traits?.is_tor_exit_node || false,
      isHosting: HOSTING_ASNS.has(asn),
      isSanctioned: countryCode ? SANCTIONED_COUNTRIES.has(countryCode) : false,
    },
    currency: countryCode ? COUNTRY_CURRENCY[countryCode] || "USD" : null,
    language: countryCode ? COUNTRY_LANGUAGE[countryCode] || "en" : null,
  };
}

// Middleware: enrich request with geo data
export async function geoMiddleware(c: any, next: any): Promise<void> {
  const ip = c.req.header("CF-Connecting-IP")
    || c.req.header("X-Forwarded-For")?.split(",")[0]?.trim()
    || c.req.header("X-Real-IP")
    || "unknown";

  if (ip === "unknown" || ip === "127.0.0.1") {
    await next();
    return;
  }

  const geo = await lookupIP(ip);
  c.set("geo", geo);

  // Block sanctioned countries
  if (geo.flags.isSanctioned) {
    return c.json({ error: "Service not available in your region" }, 451);
  }

  await next();
}

// Compliance: check if user needs GDPR consent
export function requiresGDPR(geo: GeoResult): boolean {
  const eea = new Set([
    "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
    "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
    "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO",
  ]);
  return geo.country ? eea.has(geo.country.code) : false;
}

// Compliance: California CCPA
export function requiresCCPA(geo: GeoResult): boolean {
  return geo.region === "California" && geo.country?.code === "US";
}

// Batch lookup for analytics
export async function batchLookup(ips: string[]): Promise<Map<string, GeoResult>> {
  const results = new Map<string, GeoResult>();
  const uncached: string[] = [];

  // Check cache first
  const pipeline = redis.pipeline();
  for (const ip of ips) pipeline.get(`geo:${ip}`);
  const cached = await pipeline.exec();

  for (let i = 0; i < ips.length; i++) {
    const [err, val] = cached![i];
    if (val) {
      results.set(ips[i], JSON.parse(val as string));
    } else {
      uncached.push(ips[i]);
    }
  }

  // Lookup uncached
  for (const ip of uncached) {
    const result = await lookupIP(ip);
    results.set(ip, result);
  }

  return results;
}

Results

  • $50K/year API cost eliminated — MaxMind GeoLite2 is free (GeoIP2 is $100/year for higher accuracy); local lookup costs nothing per request
  • Latency: 150ms → 0.3ms — local MMDB file lookup vs external API call; geo enrichment adds virtually zero overhead
  • OFAC compliance automated — sanctioned country traffic blocked at middleware level; compliance team has audit trail in database; zero manual review needed
  • VPN/proxy detection — hosting ASN detection flags 85% of commercial VPN traffic; combined with MaxMind proxy traits catches anonymous users attempting fraud
  • Auto-localization — currency, language, and timezone resolved from IP; new users see the right defaults without selecting anything; conversion rate up 15%