Commit Graph

7 Commits

Author SHA1 Message Date
78c28ed1bd feat(resume): admin bulk resume import
Adds the dashboard surface for the backend's /api/admin/v1/resume/import-batches
endpoints, and the multipart support it needed.

Client:
- rawRequest passes a FormData body through untouched and skips Content-Type,
  so the browser sets multipart/form-data *with the boundary*. Setting it by
  hand stripped the boundary and the server saw an empty upload — verified
  against the backend: 202 with the fix, 422 VALIDATION_ERROR without it.
  This was the first admin endpoint in the codebase to accept a file.
- New api.upload(). fetch reports no upload progress, so upload UI stays
  indeterminate rather than faking a percentage.

UI (/resume/import-batches, gated on admin:resume:read; writes on :manage):
- Upload dialog with countryCode and dry-run checked by default — seeing the
  collision report before creating accounts is almost always what you want.
- Detail sheet polls every 2s and stops once the batch is terminal. Clickable
  count tiles filter the per-resume report; step statuses surface `命中缓存` /
  `复用结果` so the dedup is visible.
- Confirm a dry run, retry failed rows, and resolve a missing contact.
  IMPORT_IDENTITY_TAKEN is shown inline — a collision is expected input, not
  an error toast.
- progress.total is 0 until unpacking finishes, so both views render 准备中…
  rather than "0 / 0", which would read as an empty batch.

types.gen.ts regenerated from the backend's admin spec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:21:15 +08:00
6ebf21ddd6 feat(db-studio): add gated entry to the database console
Adds 开发工具 → 数据库管理台, which exchanges the admin bearer token for a
single-use entry link (POST /api/admin/v1/internal-surfaces/db-studio/session)
and opens it in a new tab. The link sets an HttpOnly session cookie on the
console's own hostname, so it has to be opened by the browser rather than
fetched — the nginx gate in front of the console then re-checks the permission
on every request.

The tab is opened synchronously inside the click handler, before the request
resolves, or the popup blocker kills it; `noopener` is unusable there since it
makes window.open return null, so the opener is detached manually.

Gated on auth:db-studio:access. That key is `auth:`-prefixed rather than
`admin:` on purpose — PermissionSeeder syncs the admin role to every `admin:%`
key, which would grant database-owner SQL access to every admin account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:39:51 +08:00
1a5489a361 feat(users): integrate user info and services APIs into 3-tab detail panel with proper padding 2026-08-12 00:35:59 +08:00
2e02eee66c fix(auth): handle zero-permission users gracefully and prevent redirect loops 2026-08-11 07:36:01 +08:00
04c12ad340 feat(users): add user admin section and delegate login authorization to API 2026-08-10 23:04:41 +08:00
46056ae4b0 feat(auth): wire permission gating through menu, routes and actions
The store's can()/useCan() helpers existed but were unused. Now:
- src/auth/permissions.ts PERM catalog mirrors backend keys
- every nav item carries a permission; sidebar filter reuses can()
- requirePermission() beforeLoad guard on all child routes (direct URLs
  redirect home when the session lacks the key)
- useCan() hides/disables mutation buttons: moderation actions,
  retranslate, robot-author edit/pause, category + locale CRUD, and the
  reserved-SID assign path (admin:sid:override hint)
- 403 responses toast 无权限执行此操作; can() unit tests added

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 06:42:31 +08:00
eb617ce436 feat: api client, auth store, login, authed shell, data-table + CRUD kit 2026-07-07 22:15:08 +08:00