fix(throttler): rate-limit per real client IP instead of proxy contai…#4
Open
0xaldric wants to merge 1 commit into
Open
fix(throttler): rate-limit per real client IP instead of proxy contai…#40xaldric wants to merge 1 commit into
0xaldric wants to merge 1 commit into
Conversation
…ner IP Next.js proxy (frontend + admin) was not forwarding X-Forwarded-For to the backend, so all throttle buckets collapsed into the container IP — effectively disabling per-user rate limiting. Changes: - Add `src/common/utils/client-ip.util.ts` (shared IP extraction utility, moved from articles module which now re-exports it) - Add `RealIpThrottlerGuard` in race-result module that overrides `getTracker()` to read X-Forwarded-For / X-Real-IP headers directly, matching the existing articles module approach (no global trust proxy) - Re-enable ThrottlerModule.forRoot in race-result.module.ts (was temporarily disabled); register RealIpThrottlerGuard as provider - Replace all `@UseGuards(ThrottlerGuard)` with `RealIpThrottlerGuard` in race-result.controller.ts (6 endpoints) - Forward x-forwarded-for header in both frontend and admin proxy routes so the real nginx-injected client IP reaches the backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 9, 2026
PROD bugs phát hiện 2026-05-09 trên result.5bib.com (race FUYU live, athletes cảm nhận trực tiếp). Full BUGFIX workflow protocol — Manager init → BA PRD → Manager plan APPROVED → Coder 2 rounds → QC APPROVED WITH CAVEATS. Bug #1 — parseDistanceKm (badge.service.ts:21-66) ăn 'm' thành miles - "4800m" (4.8km) match (?:mi|miles|m)\b → ×1.60934 → 7724km → fire ULTRA badge - Fix: Option B parse meters TRƯỚC miles, regex case-sensitive ngăn '100M' regression - Test: 4800m→4.8, 5km→5, 100mi→160.93, 100M→160.93 (legacy preserved) Bug #2 — detectPodium/AgePodium/Ultra thiếu finished guard - Vendor RaceResult assign OverallRank=1 PRE-RACE → "🥇 Vô địch chung cuộc" + confetti fires cho athlete đang chạy chưa finish - Fix: helper isValidFinisher() guard finished===1 AND parseChipTime>0 ở 3 detect functions, wrappers pass-through finished+chipTime từ result doc - Test: 4 cases per function (valid finisher, finished=0, chipTime="0:00", chipTime="") Bug #3 — getCourseStats CROSS-RACE DATA LEAK - Endpoint GET /race-results/stats/:courseId thiếu raceId → aggregation $match { courseId } join TẤT CẢ races platform → counter banner FUYU hiển thị 1.567/1.231/336 thay vì 15+57=72 (data thật) - Fix: endpoint → /stats/:raceId/:courseId, service signature getCourseStats(raceId, courseId), 4 query sites filter raceId (aggregate + distinct nationality + bucketPipeline + firstDoc), cache key stats:<raceId>:<courseId> - BREAKING: SDK regen, frontend caller update raceId, old URL 404 Bug #4 — Image URL không URL-encode → ảnh bìa + ảnh cự ly không hiển thị - DB lưu raw filename Vietnamese: ".../FII_ÄAÌ£I HOÌ£ÌI THEÌÌ THAO_OFFICIAL KV.png" - Browser CSS background-image: url(...) reject malformed → silent fail - Fix: helper safeImageUrl(url) wrap encodeURI() ở 6 sites trong races/[slug]/page.tsx (banner, course cards, logo, avatars). Pattern follow precedent RaceHeroHeader.tsx:101. - BR-DISPLAY-11 ghi convention: image render PHẢI encodeURI để handle VN diacritics - Bug #5 (backend upload slugify) defer F-022 (root cause systemic, scope rộng) Pre-push CI parity gate 5/5 PASS: - Backend clean install + build + dist/main.js (3848 bytes — anti F-019 Round 4) - Admin/Frontend builds clean - TypeScript strict 0 errors trên touched files - Lockfile sync 0 diff vs origin/main Tests: - badge.service.spec.ts: 91/91 PASS (29 F-021-specific) - race-result.service.spec.ts: 18 PASS / 5 FAIL (pre-existing TD, NOT regression) Files changed: 14 (8 Scope Lock + 4 SDK auto-regen + 2 downstream JUSTIFIED) QC verdict: 🟡 APPROVED WITH CAVEATS (0 CRITICAL, 0 NEW HIGH, 100% in-scope BR coverage) - TD-F021-PURGECACHE-PATTERN-LAG (admin purgeCache stats:* old pattern → F-022) - 5 pre-existing test infra fails documented - Cache flush badge:* + stats:* sau deploy: Manager run Manager note: workflow protocol B sequential (init → PRD → plan → code 2-round → QC), KHÔNG bypass dù P0 prod incident. Lessons từ F-019 4-round deploy fail đã apply qua Pre-push CI parity gate convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 9, 2026
PROD bugs phát hiện 2026-05-09 trên result.5bib.com (race FUYU live, athletes cảm nhận trực tiếp). Full BUGFIX workflow protocol — Manager init → BA PRD → Manager plan APPROVED → Coder 2 rounds → QC APPROVED WITH CAVEATS. Bug #1 — parseDistanceKm (badge.service.ts:21-66) ăn 'm' thành miles - "4800m" (4.8km) match (?:mi|miles|m)\b → ×1.60934 → 7724km → fire ULTRA badge - Fix: Option B parse meters TRƯỚC miles, regex case-sensitive ngăn '100M' regression - Test: 4800m→4.8, 5km→5, 100mi→160.93, 100M→160.93 (legacy preserved) Bug #2 — detectPodium/AgePodium/Ultra thiếu finished guard - Vendor RaceResult assign OverallRank=1 PRE-RACE → "🥇 Vô địch chung cuộc" + confetti fires cho athlete đang chạy chưa finish - Fix: helper isValidFinisher() guard finished===1 AND parseChipTime>0 ở 3 detect functions, wrappers pass-through finished+chipTime từ result doc - Test: 4 cases per function (valid finisher, finished=0, chipTime="0:00", chipTime="") Bug #3 — getCourseStats CROSS-RACE DATA LEAK - Endpoint GET /race-results/stats/:courseId thiếu raceId → aggregation $match { courseId } join TẤT CẢ races platform → counter banner FUYU hiển thị 1.567/1.231/336 thay vì 15+57=72 (data thật) - Fix: endpoint → /stats/:raceId/:courseId, service signature getCourseStats(raceId, courseId), 4 query sites filter raceId (aggregate + distinct nationality + bucketPipeline + firstDoc), cache key stats:<raceId>:<courseId> - BREAKING: SDK regen, frontend caller update raceId, old URL 404 Bug #4 — Image URL không URL-encode → ảnh bìa + ảnh cự ly không hiển thị - DB lưu raw filename Vietnamese: ".../FII_ÄAÌ£I HOÌ£ÌI THEÌÌ THAO_OFFICIAL KV.png" - Browser CSS background-image: url(...) reject malformed → silent fail - Fix: helper safeImageUrl(url) wrap encodeURI() ở 6 sites trong races/[slug]/page.tsx (banner, course cards, logo, avatars). Pattern follow precedent RaceHeroHeader.tsx:101. - BR-DISPLAY-11 ghi convention: image render PHẢI encodeURI để handle VN diacritics - Bug #5 (backend upload slugify) defer F-022 (root cause systemic, scope rộng) Pre-push CI parity gate 5/5 PASS: - Backend clean install + build + dist/main.js (3848 bytes — anti F-019 Round 4) - Admin/Frontend builds clean - TypeScript strict 0 errors trên touched files - Lockfile sync 0 diff vs origin/main Tests: - badge.service.spec.ts: 91/91 PASS (29 F-021-specific) - race-result.service.spec.ts: 18 PASS / 5 FAIL (pre-existing TD, NOT regression) Files changed: 14 (8 Scope Lock + 4 SDK auto-regen + 2 downstream JUSTIFIED) QC verdict: 🟡 APPROVED WITH CAVEATS (0 CRITICAL, 0 NEW HIGH, 100% in-scope BR coverage) - TD-F021-PURGECACHE-PATTERN-LAG (admin purgeCache stats:* old pattern → F-022) - 5 pre-existing test infra fails documented - Cache flush badge:* + stats:* sau deploy: Manager run Manager note: workflow protocol B sequential (init → PRD → plan → code 2-round → QC), KHÔNG bypass dù P0 prod incident. Lessons từ F-019 4-round deploy fail đã apply qua Pre-push CI parity gate convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 12, 2026
…debar nav + Playwright E2E + tech debt cleanup Phase 2B scope per Manager plan 02-manager-plan.md: - 9 page /contracts/* (list, create wizard, services, partners, partners/[id], templates, [id], [id]/acceptance, [id]/payment) - 16 _components (15 spec + dropdown-action helper) - Sidebar nav group "Hợp đồng" with 4 sub-items + NEW badge - 2 Playwright spec (lifecycle E2E + pure helper component spec — 7 tests) Phase 2A tech debt resolved: - #1 totalAmountInWords VN num→words helper (vn-num-to-words.ts + 13 unit tests PASS) - #2 Articles loop {#articles}{heading}{body}{/articles} + line-items loop syntax in 9 DOCX stub templates - #4 Admin UI implemented SDK strategy: hand-typed contracts-api.ts wrapper (594 lines) port pattern from course-map-api.ts because local Mongo+Redis not running blocks pnpm generate:api — flagged as PAUSE-CODE-PHASE2-D for post-QC Phase 3 swap. Endpoint paths + payload shapes verified byte-by-byte against contracts.controller.ts. Tests: 60 backend PASS (+13 vs Phase 2A), 7 admin component pure-helper tests. Pre-push CI 5/5 PASS: 1. backend build → dist/main.js 3848 bytes 2. admin pnpm build → 43 pages + 9 contracts routes 3. backend tsc: only pre-existing vi upload-spec errors (scripts/ excluded) 4. admin tsc: only pre-existing kiosk spec syntax errors 5. pnpm-lock.yaml unchanged Status: 🟠 READY_FOR_QC Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 25, 2026
Manager Wave 1 review BLOCKING TD resolution. 2 TDs fixed before
Wave 2B backend services start.
## TD-F062-MOM-BOUNDARY-ROLLOVER 🟡 MED 🔴 BLOCKING — RESOLVED
period-resolver.ts:
- NEW shiftMonthClamped(date, months) exported helper (lines 84-127)
Clamps day to last-day-of-target-month to avoid JS setUTCMonth rollover
bug (May 31 setUTCMonth(-1) → May 1 instead of April 30).
- Updated resolveCompare('mom') branch to use shiftMonthClamped (lines 225-238)
- 8 new standalone helper tests + 5 new mom boundary regression tests
Boundary verification matrix (7/7 PASS via Node REPL + 13 Jest tests):
- May 31 → April 30 (Manager bug case fixed)
- Jan 31 → Dec 31 (cross-year, no clamp)
- Mar 29 → Feb 29 LEAP YEAR 2024 (no clamp)
- Mar 29 → Feb 28 NON-LEAP 2025 (clamp to 28)
- Mar 31 → Feb 29 LEAP YEAR 2024 (clamp from 31 to 29)
- Time preservation HH/MM/SS/MS (May 22 23:59:59.123 → April 22 23:59:59.123)
- 0 month no-op
## TD-F062-VALIDATION-COMPAREKIND 🟢 LOW — RESOLVED
repeat-athlete-rate.dto.ts:
- @isin array extend từ 4 → 6 values: +'wow' +'mom' (parity với
Wave 1 CompareKind extension)
- ApiProperty enum array + description updated
- F-026 backward compat preserved (4 original values still accepted)
Discovery: QC TD claim "6 F-026 endpoints" actually only 1 endpoint
(repeat-athlete-rate) has compareWith field. Other 5 (merchant-churn,
time-to-fill, claim-rate, geographic-demographic, refund-cancel-rate)
don't accept compareWith. TD-F062-F026-SILENT-CAPABILITY-EXPANSION
refined understanding documented IMPLEMENTATION_NOTES Section 1
Deviation #6.
## Test results
- 104/104 analytics tests PASS (Wave 1: 77 + Wave 2A: 13 new + F-058: 14)
- ZERO break F-026 backward compat
- TypeScript clean cho 3 Wave 2A files
## Files changed (3 files / 160 LoC)
- M backend/src/modules/analytics/services/period-resolver.ts (+55)
- M backend/src/modules/analytics/__tests__/period-resolver.f062.spec.ts (+104)
- M backend/src/modules/analytics/dto/repeat-athlete-rate.dto.ts (+9)
## Workflow artifacts
- 03-coder-implementation.md Wave 2A section appended (+154 LoC)
- IMPLEMENTATION_NOTES.md Wave 2A section appended (+99 LoC, 4 sub-sections:
Deviations #5+#6 + Forced #4 + Tradeoffs 5 + Reviewer Notes)
## Wave 2B next session scope
- 5 NEW backend services (runner-analytics + race-performance +
merchant-comparison + ga4 + export)
- 16 NEW DTOs + 12 NEW endpoints
- flushEventOverrideCache() extend +13 patterns
- PAUSE pnpm install @google-analytics/data — Danny confirm
- Verify MySQL races.type column existence PAUSE-SA-07
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 25, 2026
QC report flagged 2 BLOCKING + 2 MED PRD-spec drifts on commit d5e31b5. v2 patch resolves all 4: 🔴 BLOCKING #1: Endpoint URL — @get('revenue/comparison') → @get('comparison') per BR-SA-04 line 200 (NOT under /revenue/ namespace). Wave 3 frontend Tab 1 Comparison Row sẽ call /analytics/comparison?compareWith=mom per PRD journey table line 749 — now mounted at right path. 🔴 BLOCKING #2: Cache key drift (4 sub-issues fixed via helper extend) - period-resolver.ts: EXTEND buildMetricCacheKey signature (tenant scope + optional `extra` axis between scope và periodKey per BR-SA-04 spec) - analytics.service.ts: NEW resolveQueryScope + buildPeriodKey helpers; 3 inline cache key strings replaced với buildMetricCacheKey composition - Result keys now PRD-compliant: analytics:metric:weekly-revenue:tenant:42:range:2026-01-01~2026-05-25 analytics:metric:comparison:platform:mom:range:... - BR-SA-18 invalidation hook ready: Wave 2C flushEventOverrideCache() sẽ match analytics:metric:{weekly,monthly}-revenue:* + comparison:* 🟡 MED #3: Default 12 weeks / 12 months when no period params - NEW applyDefaultPeriod(query, granularity) helper - Returns NEW query object (no mutation) với from = today - 84/365 days - Called first line trong getWeeklyRevenue + getMonthlyRevenue 🟡 MED #4: buildMetricCacheKey tenant scope extension Fixed as part of BLOCKING #2 (single helper extension serves both) Tests: 169/169 PASS (161 baseline + 8 NEW invariant tests): - revenue-endpoints.f062.spec.ts: cache key helper usage assertions, endpoint URL @get('comparison') guard, default period invariants, extractMethodBody helper generalized to non-async methods - period-resolver.f062.spec.ts: NEW 3 tests cho tenant scope + extra axis + backward compat 0 regression. tsc clean. Backward compat preserved (existing 3-arg + race scope buildMetricCacheKey calls unaffected). IMPLEMENTATION_NOTES.md Section 1 Deviation #10 — honest root-cause analysis why initial Self-Review Bước 2 missed these (pattern-matched Response shape only, không grep PRD bullet keywords Endpoint/Default/Cache). Lesson codified cho Wave 5 memory update. Status: READY_FOR_QC v2 (re-submit). Pending QC re-verify 4 fix items. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
minhnb9897
added a commit
that referenced
this pull request
May 25, 2026
All 4 REJECT findings from QC report Wave 2B-1 (commit d5e31b5) verified RESOLVED by fix patch commit a36d3b6: - BLOCKING #1 Endpoint URL /analytics/comparison ✓ - BLOCKING #2 Cache key drift (4 sub-issues) ✓ - MED #3 Default 12 weeks/12 months ✓ - MED #4 buildMetricCacheKey tenant scope extend ✓ PRD Compliance Score: 19/19 ✅ (was 13/19 ✅ in v1). 169/169 tests PASS (was 161 + 8 NEW anti-regression invariants). 0 regression. Backward compat preserved. Verdict: APPROVED — ready for Manager spot-check (independent code review). Reviewer Notes top-5 revised priority in IMPLEMENTATION_NOTES Section 4. Defense-in-depth value documented: v1 had 161 tests PASS but 4 PRD spec drifts; QC Phase 5 line-by-line walk caught them; v2 + 8 NEW invariants prevent re-introduction. Lesson codified for Wave 5 memory. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…ner IP
Next.js proxy (frontend + admin) was not forwarding X-Forwarded-For to the backend, so all throttle buckets collapsed into the container IP — effectively disabling per-user rate limiting.
Changes:
src/common/utils/client-ip.util.ts(shared IP extraction utility, moved from articles module which now re-exports it)RealIpThrottlerGuardin race-result module that overridesgetTracker()to read X-Forwarded-For / X-Real-IP headers directly, matching the existing articles module approach (no global trust proxy)@UseGuards(ThrottlerGuard)withRealIpThrottlerGuardin race-result.controller.ts (6 endpoints)