Files
MyIP/api/AGENTS.md
T
jason5ng32andClaude Opus 4.7 c7ef2cb810 Docs: split AGENTS.md into root + frontend + api; slim CLAUDE.md
CLAUDE.md is now a 10-line pointer to AGENTS.md plus a Claude-specific
extension slot; everything contributor-facing lives in AGENTS.md.

Root AGENTS.md is the index — Overview, Stack, Commands, Project layout,
universal Conventions (language / comments / i18n), Testing, a short
Security & Boundaries summary, and Workflow. It references
@frontend/AGENTS.md and @api/AGENTS.md for area-specific details.

frontend/AGENTS.md collects the UI system (primitives, design tokens,
status tones, canonical patterns) plus the shadcn-vue-first rule and
frontend-specific project layout / conventions. This is what a UI
contributor needs — API rules never get in the way.

api/AGENTS.md covers the backend handler shape (fetchUpstream, error
shape, response normalization), the middleware model (requireReferer
globally, requireValidIP per-route, handlers must not repeat those
checks), the private-API header pass-through exception, and the smoke
test layout.

New rules captured across the three files:

- Every new file opens with a header comment stating its purpose
- Large templates / functions carry block comments on each meaningful section
- Big features and modifications to tested features update tests in the
  same change; `npm run check` must be green before handing off
- One concern per commit
- Visual changes get an explicit "please test in npm run dev" handoff
- User testing must pass before commit
- shadcn-vue-first for new UI components (local primitives → shadcn-vue
  library → hand-rolled Tailwind as last resort)
- Every commit scans AGENTS.md for staleness and updates it in the same
  change if the rules shifted

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 09:06:13 +08:00

5.4 KiB

api/AGENTS.md

Conventions for Express 5 back-end handlers under api/ and the shared back-end code under common/. See ../AGENTS.md for universal project rules (language, commits, testing expectations).

Overview

The Express app is defined in backend-server.js at the repo root. Every route is wired there and delegated to a handler module under api/. Shared code (referer check, IP validator, fetch helper, Express middleware, MaxMind service) lives under common/ and is also consumed by the frontend where it makes sense (e.g. valid-ip.js, fetch-with-timeout.js).

Project layout

api/
├── configs.js                   ← /api/configs — reports which env-gated features are on
├── google-map.js                ← /api/map — Google Static Maps image proxy (binary stream)
├── ipinfo-io.js, ipapi-com.js, ipapi-is.js, ip2location-io.js, ip-sb.js,
│   ipcheck-ing.js, maxmind.js   ← IP geolocation source handlers (route per source)
├── invisibility-test.js         ← /api/invisibility — proxy to private IPCheck.ing endpoint
├── mac-checker.js               ← /api/macchecker — MAC vendor lookup
├── get-whois.js                 ← /api/whois — whoiser wrapper
├── cf-radar.js                  ← /api/cfradar — ASN details via Cloudflare Radar
├── dns-resolver.js              ← /api/dnsresolver — DNS + DoH parallel query
├── get-user-info.js             ← /api/getuserinfo — user-profile proxy
└── update-user-achievement.js   ← /api/updateuserachievement — user-achievement proxy

common/
├── fetch-with-timeout.js        ← fetchWithTimeout (5s default) + fetchUpstream (8s preset)
├── guards.js                    ← requireReferer + requireValidIP Express middleware
├── referer-check.js             ← low-level referer allow-list check
├── valid-ip.js                  ← IPv4 / IPv6 validator (also re-exported from frontend)
├── maxmind-service.js           ← mmdb reader + lookup
├── maxmind-updater.js           ← scheduled mmdb auto-update
└── maxmind-db/                  ← GeoLite2-ASN.mmdb + GeoLite2-City.mmdb

Conventions

  • Handler shape. Each handler is a single default export, async (req, res) => …. It reads from req.query / req.body, calls upstream, and writes one response.

  • Every upstream HTTP call uses fetchUpstream from common/fetch-with-timeout.js:

    import { fetchUpstream } from '../common/fetch-with-timeout.js';
    
    const apiRes = await fetchUpstream(url);
    const json = await apiRes.json();
    res.json(transform(json));
    

    Default timeout is 8s. Never add a bare fetch() or https.get() — if a provider hangs, the Express connection should time out, not pin indefinitely.

  • Error shape. res.status(500).json({ error: error.message }) on upstream failures; res.status(400).json({ error: '…' }) on bad input. Be terse — the frontend doesn't display these error strings verbatim.

  • Response shape. IP-geolocation handlers normalize their upstream's response into the canonical shape consumed by the frontend (ip / country / country_name / country_code / latitude / longitude / asn / org / …). If you add a new source, match the existing shape.

Security & Boundaries

Guards live in middleware, not in handlers

  • requireReferer is mounted globally on /api/* in backend-server.js. It rejects any request whose Referer header isn't on the ALLOWED_DOMAINS list (plus localhost always). Handlers must not repeat the referer check.
  • requireValidIP() is attached per-route to every handler that takes ?ip=. It rejects missing or malformed IPs before the handler runs. Handlers must not repeat the IP check — inside the handler body, req.query.ip is already known to be a well-formed string.
  • If you add a new handler that needs a different-shape param guard, add the guard to common/guards.js and attach it in backend-server.js rather than open-coding the check in the handler.

Private-API header pass-through (intentional exception)

Handlers that call our own private IPCheck.ing API (ipcheck-ing.js, invisibility-test.js, update-user-achievement.js, get-user-info.js) forward the caller's request headers to the upstream:

const apiResponse = await fetchUpstream(url, { headers: { ...req.headers } });

This is deliberate — the upstream expects caller context (Accept-Language, auth tokens, etc.). Do not replicate this pattern for third-party upstreams; third-party handlers should send only what's explicitly needed.

Defensive method gates

Some handlers keep a req.method !== 'GET' (or PUT) branch even though Express routes already gate the method. These exist because dedicated smoke tests assert on that branch directly against the handler. If you add or copy a handler, leave the defensive gate in place if a test covers it.

Testing

  • Smoke tests for every handler live in tests/api-handlers.test.js. They cover method gating, param presence / validity (beyond what middleware handles), and the "API key missing" early-return branches.
  • Never hit real upstream APIs in tests — assert on branches that return before the first fetchUpstream.
  • Middleware itself is covered by tests/guards.test.js (referer + IP validation). Don't duplicate those assertions at the handler level.
  • fetchUpstream / fetchWithTimeout timing + abort behavior is covered by tests/fetch-with-timeout.test.js.