Original Request
Deduplicate IP detection logic into shared utility
Context: IP address detection (IPv4/IPv6 regex matching for Wikipedia anonymous editors) appears in at least 3 places: packages/server/src/routes/rankedFeed.ts, packages/core/src/composables/useReviewFeed.ts, and potentially other locations. Each implements its own regex. This violates DRY and means bug fixes to the pattern need to be applied in multiple places. The fix should extract this into a shared utility in @doublecheck/core (e.g., core/src/utils/ip.ts with isIPAddress(username: string): boolean) and import it everywhere. This also enables proper unit testing of the IP detection logic in one place.
Agent's Two Cents (could be wrong)
Everything below is the AI agent's best guess based on the current codebase.
Take with a grain of salt — the original request above is the only thing that came from a human.
Problem / Motivation
The same isIPUser() function is copy-pasted in at least two files with identical regex logic for detecting Wikipedia anonymous editors (IPv4 via ^\d{1,3}(\.\d{1,3}){3}$ and IPv6 via ^[0-9a-fA-F:]+$ with colon check). If a bug is found in the pattern — e.g., the IPv6 regex is overly permissive and would match strings like a:b — the fix must be applied in every copy. This is a classic DRY violation that grows worse over time.
Proposed Solution
Create a shared utility module in @doublecheck/core (e.g., packages/core/src/utils/ip.ts) exporting an isIPAddress(username: string): boolean function. Replace all duplicated isIPUser() implementations with imports from this shared module. The server package already depends on core, so the import graph is clean.
Dependencies & Potential Blockers
No major blockers identified. The server package already imports from @doublecheck/core, so adding another import is straightforward. The packages/core/src/utils/ directory does not yet exist and will need to be created, plus a barrel export added to core/src/index.ts.
How to Validate
Scope Estimate
Small
Key Files/Modules Likely Involved
packages/core/src/utils/ip.ts (new — shared utility)
packages/core/src/index.ts (add export)
packages/core/src/composables/useReviewFeed.ts (replace local isIPUser)
packages/server/src/routes/rankedFeed.ts (replace local isIPUser)
packages/core/src/__tests__/ip.test.ts (new — unit tests)
Rough Implementation Sketch
- Create
packages/core/src/utils/ip.ts with a single exported isIPAddress() function containing the consolidated IPv4/IPv6 regex logic
- Add a barrel export from
packages/core/src/index.ts
- In
rankedFeed.ts: remove the local isIPUser function, import isIPAddress from @doublecheck/core
- In
useReviewFeed.ts: remove the local isIPUser function, import isIPAddress from @doublecheck/core
- Write unit tests covering IPv4, IPv6, non-IP strings, and edge cases
- Consider tightening the IPv6 regex while consolidating (current pattern
^[0-9a-fA-F:]+$ is loose)
Open Questions
- Should the IPv6 regex be tightened during this refactor, or kept identical to the current (loose) pattern and improved in a follow-up? The current regex accepts strings like
a:b which are not valid IPv6 addresses.
- Should the function name be
isIPAddress (as suggested) or keep the existing isIPUser convention? The former is more precise about what it checks; the latter matches existing call sites.
- The leaderboard route (
packages/server/src/routes/leaderboard.ts) filters anonymous users via a MongoDB $match against the literal string "anonymous" — should this also be updated to use IP detection, or is it a different concern?
Potential Risks or Gotchas
- The IPv6 regex is quite permissive — any string of hex chars and colons with at least one colon matches. Tightening it during this refactor could change filtering behavior if any real usernames happen to match the loose pattern but not a strict one. Recommend keeping the existing behavior first, then tightening in a separate PR with explicit test coverage.
- The userscript package has its own
detectIdentity() mechanism for anonymous users (checking mw.config.get("wgUserIsAnon")) which is a different concern (MediaWiki runtime detection vs. username-string parsing) — it should NOT use this utility.
Original Request
Agent's Two Cents (could be wrong)
Problem / Motivation
The same
isIPUser()function is copy-pasted in at least two files with identical regex logic for detecting Wikipedia anonymous editors (IPv4 via^\d{1,3}(\.\d{1,3}){3}$and IPv6 via^[0-9a-fA-F:]+$with colon check). If a bug is found in the pattern — e.g., the IPv6 regex is overly permissive and would match strings likea:b— the fix must be applied in every copy. This is a classic DRY violation that grows worse over time.Proposed Solution
Create a shared utility module in
@doublecheck/core(e.g.,packages/core/src/utils/ip.ts) exporting anisIPAddress(username: string): booleanfunction. Replace all duplicatedisIPUser()implementations with imports from this shared module. The server package already depends on core, so the import graph is clean.Dependencies & Potential Blockers
No major blockers identified. The server package already imports from
@doublecheck/core, so adding another import is straightforward. Thepackages/core/src/utils/directory does not yet exist and will need to be created, plus a barrel export added tocore/src/index.ts.How to Validate
packages/core/src/utils/ip.tsexists and exportsisIPAddress()packages/core/src/__tests__/ip.test.tscover: valid IPv4 (1.2.3.4,192.168.0.1), valid IPv6 (2001:0db8::1,::1), non-IP usernames (JohnDoe,Bot123), edge cases (empty string, partial matches like999.999.999.999)packages/server/src/routes/rankedFeed.tsimports from@doublecheck/coreinstead of defining its ownisIPUserpackages/core/src/composables/useReviewFeed.tsimports from the shared utility instead of defining its ownisIPUserpnpm buildsucceeds across all packagespnpm test)Scope Estimate
Small
Key Files/Modules Likely Involved
packages/core/src/utils/ip.ts(new — shared utility)packages/core/src/index.ts(add export)packages/core/src/composables/useReviewFeed.ts(replace localisIPUser)packages/server/src/routes/rankedFeed.ts(replace localisIPUser)packages/core/src/__tests__/ip.test.ts(new — unit tests)Rough Implementation Sketch
packages/core/src/utils/ip.tswith a single exportedisIPAddress()function containing the consolidated IPv4/IPv6 regex logicpackages/core/src/index.tsrankedFeed.ts: remove the localisIPUserfunction, importisIPAddressfrom@doublecheck/coreuseReviewFeed.ts: remove the localisIPUserfunction, importisIPAddressfrom@doublecheck/core^[0-9a-fA-F:]+$is loose)Open Questions
a:bwhich are not valid IPv6 addresses.isIPAddress(as suggested) or keep the existingisIPUserconvention? The former is more precise about what it checks; the latter matches existing call sites.packages/server/src/routes/leaderboard.ts) filters anonymous users via a MongoDB$matchagainst the literal string"anonymous"— should this also be updated to use IP detection, or is it a different concern?Potential Risks or Gotchas
detectIdentity()mechanism for anonymous users (checkingmw.config.get("wgUserIsAnon")) which is a different concern (MediaWiki runtime detection vs. username-string parsing) — it should NOT use this utility.