- ParsePackageUseCase.execute now accepts onProgress callback
- RegisterVersionUseCase added for the new /register endpoint
- use-upload.ts now imports only from application layer (no direct infra import)
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>
refreshAccessToken() was discarding the new refresh token returned by
/auth/refresh, reusing the old (now-invalidated) one on next expiry.
This caused the second refresh to return 401, kicking the user to login
after just 15 minutes (two access token lifetimes).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously any refresh failure (network error, service restart, timeout)
would clear localStorage and redirect to /login — kicking active users.
Now only a deliberate token rejection (HTTP 401/403) causes logout.
Transient errors are rejected silently without destroying the session.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
File select now auto-parses the APK/IPA (sends to /parse endpoint),
auto-fills versionName/buildNumber/minOsVersion, and keeps the upload
button disabled (isParsing=true) until parsing finishes — matching
RWADurian's proven UX exactly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
File was being uploaded twice:
1. POST /parse on file select (for form auto-fill)
2. POST /upload on submit
Remove the parse network call entirely. Server already parses the APK/IPA
buffer as part of the upload handler. User fills form fields manually.
Single upload, single public-internet transfer.
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>
Backend wraps data in extra layer:
system-health → {code:0, data:{services:[...]}}
realtime-trades → {code:0, data:{items:[...], total:N}}
HttpClient strips outer data but leaves inner object.
Fix: type as {services/items} and access nested arrays.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth.store: eslint-disable with explicit comment for intentional infra access
(session orchestration is a designated cross-layer boundary)
- Add auth.use-cases.ts (LoginUseCase / LogoutUseCase) for use by views/hooks
- Fix no-explicit-any in AppVersionManagementPage (use unknown + type assertion)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instance-level default Content-Type: application/json was overriding
browser's auto-generated multipart/form-data boundary. Remove it for
FormData so browser sets correct Content-Type with boundary.
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>
- Prevent TypeError if useApi returns non-array shape
- Add HttpClient.get logging to trace raw vs unwrapped response
- Parse timeout: 120s → 300s (matches upload, avoids timeout on large files)
- Show hint for large files (>30MB) during parse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth.store: persist refreshToken alongside accessToken
- http.client: on 401, auto-refresh token and retry original request
with mutex lock to prevent concurrent refresh calls; only redirect
to /login if refresh itself fails
- upload modal: restore auto-parse on file select; show warning if
parse fails; add console logs for debugging; fix button disabled
during parsing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove auto-parse on file select (was uploading 48MB twice, took 100+ sec)
- Backend /upload already parses APK internally, version fields are now optional
- Show file name + size after selection
- Show progress hint during upload
- Better error extraction from API response
- Clear error when new file is selected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Manually setting Content-Type: multipart/form-data without the boundary
causes the server to reject the request. Axios automatically sets the
correct header with boundary when FormData is passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create (admin) route group with AdminLayout wrapper
- Add page.tsx route files for all 25 view pages (dashboard, issuers,
users, trading, risk, compliance, system, disputes, coupons, finance,
chain, reports, merchant, agent, insurance, analytics sub-pages,
compliance sub-pages)
- Update AdminLayout to use Next.js usePathname/useRouter for real
URL-based navigation instead of internal state
- Add 'use client' directive to view components using useState hooks
- Fix 404 on /dashboard by creating proper App Router route structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Next.js treats `src/pages/` as the Pages Router and requires every file
to have a default export. Our page components use named exports
(e.g. `export const DashboardPage`) since they are UI view components,
not Next.js route handlers.
Rename to `src/views/` so Next.js only uses the App Router (`src/app/`).
The App Router page.tsx files will import from `@/views/` as needed.
24 files moved: src/pages/**/*.tsx → src/views/**/*.tsx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The milestones array only had 'progress' and 'pending' status values,
causing TS to infer the status type as 'progress' | 'pending'. The
template code then compared m.status === 'done' which TS flagged as
unreachable. Fix by explicitly typing the array to include all three
possible status values: 'done' | 'progress' | 'pending'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The initial project has no node_modules or package-lock.json yet.
Use wildcard COPY for lock file and fallback to `npm install`
when lock file doesn't exist. Once lock file is generated and
committed, it will automatically use the faster `npm ci`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>