Backend entity has no type column yet; whitelist the type field in DTO
so forbidNonWhitelisted doesn't reject requests from the mobile app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Users registered before referral-service was deployed have no profile row.
getMyInfo() now auto-provisions a profile on first access instead of
throwing NotFoundException.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Android DNS fix (native_dio_adapter):
- Dart's getaddrinfo() fails on some Android 12+ devices when browser works
fine — root cause is Dart native socket using different DNS path than
Android Java stack; fix by using NativeAdapter (OkHttp) on Android
- Add native_dio_adapter ^1.5.1 to pubspec; enable in ApiClient for Android
Message page bugs (5 fixes):
- Fix offset->page: Flutter sent ?offset=0 but backend NotificationQueryDto
uses page/limit (1-based); now sends ?page=1&limit=50
- Fix announcement tab API: Tab 2 now calls /api/v1/announcements (separate
resource) instead of /api/v1/notifications?type=ANNOUNCEMENT
- Fix markAllAsRead routing: calls correct API based on active tab
- Fix markAsRead routing: announcement items use announcements API
- Fix detail navigation: passes NotificationItem as route argument
Backend notification-service:
- Add PUT /api/v1/notifications/read-all endpoint
- Add markAllReadByUserId() to repository interface + TypeORM implementation
- Add markAllRead() to NotificationService
i18n (4 languages): add time.justNow/minutesAgo/hoursAgo/daysAgo keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the flow uploaded the 53MB file twice:
1. POST /parse → parse metadata (file discarded)
2. POST /upload → parse again + save (file sent again)
New flow — file sent exactly once:
1. POST /parse → upload file, save to disk, parse metadata
returns {versionName, versionCode, minSdkVersion, storageKey, fileSize, fileSha256}
2. POST /register → JSON only (no file), creates DB record using storageKey
Frontend:
- handleFileChange: async, immediately uploads to /parse with progress bar (0-100%)
- handleSubmit: calls /register with storageKey + form metadata (instant)
- Upload modal: real-time progress bar, "confirm" button disabled until parse complete
- Console logs at every step for debugging
Backend:
- POST /parse: saves file after parsing, returns storageKey in response
- POST /register: new endpoint, accepts JSON + storageKey, creates version record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Logs file size on receipt, parse duration, file save duration, and
total request time — to pinpoint where upload latency comes from.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the APK/IPA upload required two full public-internet transfers:
1. POST /parse → browser → gateway → admin-service (full file, for metadata)
2. PUT presigned → browser → oss.gogenex.com / MinIO (full file, to store)
Now follows the same pattern as RWADurian admin-service:
- Single multipart POST /admin/versions/upload
- admin-service parses buffer in-memory (yauzl / unzipper)
- Saves to local disk (UPLOAD_DIR env, default ./uploads)
- Download served via existing GET /app/version/download/:id (streams local file)
Changes:
- file-storage.service.ts: drop minio dep, use fs/promises + crypto
- admin-version.controller.ts: POST upload now accepts multipart file,
removes GET presigned-url endpoint (no longer needed)
- version.repository.ts (frontend): single FormData POST, removes
three-step presigned-URL flow
Result: file crosses public internet once instead of twice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- infrastructure/kong/: Kong declarative config for gateway server
All service URLs use http://192.168.1.222:PORT (internal server)
admin-service gets extended timeouts (300s) for large uploads
- docker-compose.yml: admin-service uses MINIO_ENDPOINT=192.168.1.200:9200
Plain HTTP via Nginx internal proxy (no SSL, no extra_hosts needed)
New upload path:
Browser → Nginx:443 → Kong:48080 (local) → admin-service(LAN) → MinIO:9200(local)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Container maps oss.gogenex.com → 192.168.1.200 (LAN IP) so it
connects to Nginx:443 which proxies to localhost:9100 (MinIO).
Port 443 is already open in UFW; avoids hairpin NAT and raw iptables
drop rules that block direct access to 192.168.1.200:9100.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
admin-service runs on 192.168.1.222 (LAN). Connecting to MinIO via
public IP 154.84.135.121 fails with ETIMEDOUT (hairpin NAT). Use
internal gateway IP 192.168.1.200:9100 (no SSL) for S3 API calls.
Public download URLs still use https://oss.gogenex.com.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause was proxy_request_buffering off in Nginx gateway (already removed).
Kong should use default settings, matching IT0 reference architecture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add KONG_NGINX_PROXY_PROXY_REQUEST_BUFFERING=off so Kong streams
the request body to admin-service without buffering to disk.
Root cause: Nginx streams (proxy_request_buffering off) → Kong buffers
to disk → Kong returns 400 with empty body when forwarding to upstream.
Fix: Kong also streams, matching Nginx's streaming behavior end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without boundary multer receives undefined file. Also add guards in
backend parse/upload to avoid crash if file is missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ManifestParser(buffer) 内部自带 BinaryXmlParser,无需先调用 BinaryXmlParser.parse()
再把结果传入 ManifestParser,否则导致 readUInt16LE is not a function。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter wallet_provider.dart calls /api/v1/wallet/balance but the
controller only had GET /wallet (root). Adding /wallet/balance alias.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 6 service strategies were returning { sub } but controllers use req.user.id.
Change return value from { sub: payload.sub } to { id: payload.sub } so that
req.user.id resolves correctly in all protected controllers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notification-service was missing JWT_ACCESS_SECRET, causing it to use the
fallback 'dev-access-secret' instead of 'dev-access-secret-change-in-production'.
This made all auth-protected endpoints return 401, triggering logout in the app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add issuer-service/infrastructure/strategies/jwt.strategy.ts (was omitted
from previous commit, causing build failure)
- Wrap search hint Text in Expanded to prevent Row overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 services (user/issuer/clearing/compliance/notification/telemetry) were
missing JwtStrategy provider, causing 'Unknown authentication strategy jwt'
500 errors on all auth-protected endpoints.
- Create infrastructure/strategies/jwt.strategy.ts in each service
- Update each module to import from local path (not @genex/common)
- Revert @genex/common/index.ts strategy export (passport-jwt not in each
service's node_modules, causes runtime 'Cannot find module' error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>