9 Commits
Author SHA1 Message Date
jason5ng32andClaude Opus 5.5 c29e48687b Refactor(layout): split backend-only code out of common/, group frontend utils
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>
2026-10-05 13:10:02 +08:00
jason5ng32andClaude Opus 4.8 94a0676bb9 Refactor(api): extract makeGeoHandler factory for geo source handlers
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 10:08:31 +08:00
jason5ng32andClaude Opus 4.7 833f66cc42 Feat(backend): introduce pino logger + pino-http request logs
Replace all backend console.* with a shared pino singleton at
common/logger.js. Startup-style lines get emoji prefixes (🚀 listening,
📦 maxmind ready, 📥 downloading, 🗓️ schedule, ⚠️ warning, ❌ failure);
per-handler / per-request logs stay plain.

Three .env-driven knobs, all without NODE_ENV:
  - LOG_LEVEL (default info) — pino level filter
  - LOG_FORMAT (default pretty) — "json" for log shippers
  - LOG_HTTP (default off) — pino-http on /api/* gated behind this flag
    so pm2 logs don't bloat with 2xx/3xx request lines on every deploy;
    when enabled, 4xx log as warn and 5xx as error

logger.js calls dotenv.config({ quiet: true }) itself because ES module
imports are hoisted above backend-server.js's dotenv call — without that
LOG_LEVEL from .env was silently ignored.

Fixes in common/rdap.js:
  - err: res.status → status: res.status (pino's err serializer expects
    an Error object, not a number)
  - broken single-quoted template literal in "No RDAP endpoint" message
  - demote "No RDAP endpoint" and "Domain not found" from error to warn
    (they're expected outcomes for user input, not server failures)

README (en/zh/fr/tr) + .env.example + AGENTS.md (root and api/) all
updated to document the three new vars and the "no console.* in backend"
convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:29:52 +08:00
jason5ng32 f8c5851686 Improve comments 2026-04-19 08:24:49 +08:00
jason5ng32andClaude Opus 4.7 2792185a53 refactor(common): unify fetch-with-timeout + fetch-upstream into one helper
The two wrappers (frontend/utils/fetch-with-timeout.js and
common/fetch-upstream.js) were ~95% identical — same AbortController +
setTimeout pattern, same caller-signal chaining, differing only in
default timeout (5s vs 8s) and which half of the app imported them.
Their tests were word-for-word duplicates too.

Consolidate to common/fetch-with-timeout.js with two named exports:

- fetchWithTimeout(url, init) — 5s default (front-end use)
- fetchUpstream(url, init)    — 8s preset around fetchWithTimeout (back-end)

- frontend/utils/fetch-with-timeout.js becomes a thin re-export of the
  common module (same pattern we use for valid-ip.js). Front-end imports
  don't change.
- All 12 back-end handler imports move from './common/fetch-upstream.js'
  to './common/fetch-with-timeout.js'.
- tests/fetch-upstream.test.js is deleted; tests/fetch-with-timeout.test.js
  now covers both exports (10 cases total).
- common/fetch-upstream.js deleted.

Net: -1 file, -130 lines of duplicate code & tests; single source of truth
for the timeout logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 20:05:20 +08:00
jason5ng32andClaude Opus 4.7 d51d575efd refactor(backend): unify upstream HTTP via fetchUpstream; kill https.get
All 16 handlers previously mixed two HTTP client styles:

- 7 used Node's built-in https.get()/http.get() with callback events
  (data/end/error) and 20+ lines of JSON accumulation
- 4 used modern fetch() but without any timeout — upstream hangs would
  pin the Express connection indefinitely

Every upstream request now goes through common/fetch-upstream.js, which
wraps fetch() with an 8s AbortController timeout. Handlers shrink from
~20 lines of request boilerplate to a 5-line try/catch:

    try {
        const apiRes = await fetchUpstream(url);
        const json = await apiRes.json();
        res.json(modifyJson(json));
    } catch (e) {
        res.status(500).json({ error: e.message });
    }

Converted handlers:
- ipinfo-io / ipapi-com / ipapi-is / ip2location-io / ip-sb / mac-checker
  (dropped `import { get } from 'https'/'http'`, rewrote the request)
- ipcheck-ing / invisibility-test / update-user-achievement / get-user-info
  (already on fetch, swapped to fetchUpstream to gain the timeout)
- cf-radar (inline fetchFromCloudflare helper swapped to fetchUpstream)

Special case: google-map.js streams a binary JPEG to the client rather
than parsing JSON. Uses Readable.fromWeb(apiRes.body).pipe(res) to
bridge the fetch ReadableStream to the Express response.

Net -150 lines of handler code; upstream hangs now cap at 8s instead
of 30-120s; consistent error handling across all handlers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:56:13 +08:00
jason5ng32andClaude Opus 4.7 4ae07831f6 refactor(backend): mount requireValidIP; strip per-handler IP checks
7 handlers that take ?ip (ipinfo, ipapicom, ipchecking, ipsb, ipapiis,
ip2location, maxmind) each opened with the same 8-line block:

    const ipAddress = req.query.ip;
    if (!ipAddress) return res.status(400).json({ error: 'No IP...' });
    if (!isValidIP(ipAddress)) return res.status(400).json({ ... });

Replace by attaching requireValidIP() to the matching routes in
backend-server.js. The handler bodies now assume ?ip has been validated
upstream and just read it:

    const ipAddress = req.query.ip;

Handlers that accept other param shapes (ASN, MAC, domain, user ID) or
no params are untouched — they retain whatever handler-specific
validation they need.

Tests updated:
- tests/api-handlers.test.js: drop the 2×3 = 6 "rejects missing/malformed
  ip" cases for ip-sb / ipapi-com / ipinfo-io, the 2×2 = 4 for
  ipapi-is / ip2location-io, and the 2 for ipcheck-ing. Each set was
  testing what the middleware now owns; tests/guards.test.js covers it.
  The "reports missing API key after IP passes validation" case for
  ipcheck-ing stays — that exercises handler logic beyond the middleware.
- tests/api-smoke.test.js: drop the maxmind "rejects missing and invalid
  IP parameters" case (moved to middleware).

Net -13 tests vs pre-commit; 195 still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:53:00 +08:00
jason5ng32andClaude Opus 4.7 49e557212b refactor(backend): mount requireReferer globally; strip per-handler checks
Every handler under api/ used to open with the same 4-line referer gate:

    const referer = req.headers.referer;
    if (!refererCheck(referer)) {
        return res.status(403).json({...});
    }

Replace that with `app.use('/api', requireReferer)` in backend-server.js
(from common/guards.js, landed in the previous commit). The middleware
fires before any handler runs, so by the time execution reaches a
handler, the referer is guaranteed to be on the allow-list. 16 handlers
each lose ~4 lines + one import.

api/configs.js is a special case — it still READS req.headers.referer
to classify whether the caller is the canonical ipcheck.ing deployment
("originalSite") or a self-hosted fork. No longer rejects, just reads.

Tests updated:
- tests/api-handlers.test.js: drop the 24-case "handlers reject missing
  referer" parametrized suite. Its concern moved to middleware; covered
  by tests/guards.test.js.
- tests/api-smoke.test.js: drop the 4 "rejects without referer" cases
  for configs / google-map / maxmind / dns-resolver for the same
  reason. Non-referer assertions (method gating, invalid params,
  successful config read) stay.

Net -208 lines vs pre-commit; 208 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:50:26 +08:00
jason5ng32 c4c7021951 Code Improvements 2025-02-03 17:00:34 +08:00