mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
common/ had become the backend's library: 30 of its 41 modules were imported by nothing in the frontend, so the name no longer said what belonged there. It now holds only what the frontend or the build imports too; backend-only code moved to server/, flat except for datasets/ (the dataset engine and its readers). The *-db/ dataset directories stay in common/: self-hosting docs and Docker volumes mount those paths. frontend/utils/ keeps its app-wide modules at the root and gains ip/ (IP / ASN shaping and validation), report/ (the report pipeline) and features/ (the pure layer of a single feature). Paths only — no logic changes. The Dockerfile copies server/, and the AGENTS.md files say where a new module goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.1 KiB
JavaScript
47 lines
2.1 KiB
JavaScript
// Factory for IP-geolocation source handlers.
|
|
//
|
|
// Every geo source (ipinfo.io, ip-api.com, ipapi.is, ip2location.io, ip.sb)
|
|
// shares an identical Express shell: read the (already-validated) ?ip,
|
|
// build a source-specific URL, fetch it through fetchUpstream, normalize the
|
|
// upstream JSON into the canonical response shape, and respond — with a
|
|
// uniform try/catch that logs the error and returns a 500.
|
|
//
|
|
// makeGeoHandler captures that shell. Each source supplies only:
|
|
// - name : short id used in the error log message
|
|
// - buildUrl : (req) => string URL OR { url, logContext } when the
|
|
// handler wants extra fields (e.g. lang) on the error log
|
|
// - normalize : (json) => canonical response object
|
|
//
|
|
// buildUrl runs before the try block so any env-key selection it performs
|
|
// keeps its current behavior (e.g. throwing on a missing key surfaces the
|
|
// same way the original inline handlers did).
|
|
|
|
import { fetchUpstream } from '../common/fetch-with-timeout.js';
|
|
import logger from './logger.js';
|
|
|
|
export function makeGeoHandler({ name, buildUrl, normalize }) {
|
|
return async (req, res) => {
|
|
// Presence, validity and public routability guaranteed by the
|
|
// requirePublicIP middleware — a reserved address never reaches here.
|
|
const ipAddress = req.query.ip;
|
|
|
|
const built = buildUrl(req);
|
|
const url = typeof built === 'string' ? built : built.url;
|
|
const logContext = typeof built === 'string' ? {} : (built.logContext || {});
|
|
|
|
try {
|
|
const apiRes = await fetchUpstream(url);
|
|
// Outage / gateway pages come back as HTML — fail on status
|
|
// instead of letting JSON.parse throw on "<html>".
|
|
if (!apiRes.ok) {
|
|
throw new Error(`Upstream responded ${apiRes.status}`);
|
|
}
|
|
const json = await apiRes.json();
|
|
res.json(normalize(json));
|
|
} catch (e) {
|
|
logger.error({ err: e, ip: ipAddress, ...logContext }, `${name} handler failed`);
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
};
|
|
}
|