Files
gig-admin/README.md
kongkx 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

100 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# gig-admin
Standalone admin dashboard for the gig-platform backend (`/api/admin/v1/*`).
React 19 · TanStack Start (SPA mode) · TanStack Query/Table · shadcn/ui · Tailwind v4 · TypeScript.
## Quick start
```bash
cp .env.example .env # VITE_API_BASE_URL → backend origin
pnpm install
pnpm dev # http://localhost:3000
```
Log in with an admin-role account (Passport password grant against
`POST /api/admin/v1/auth/login/password`). To seed a local admin, run in the
backend repo:
```bash
ddev exec php artisan tinker storage/app/e2e-admin-seed.php
# → admin@gig.local / Admin@12345
```
## Scripts
| Command | Purpose |
| --- | --- |
| `pnpm dev` | dev server on :3000 |
| `pnpm build` / `pnpm preview` | production build / preview |
| `pnpm typecheck` | `tsc --noEmit` |
| `pnpm test` | vitest (API client envelope/refresh/error tests) |
| `pnpm gen:api` | regenerate `src/api/types.gen.ts` from the backend's private admin OpenAPI spec (`../gig-platform/storage/app/api-docs/admin/openapi.yaml`; run `ddev artisan api-docs:generate` there first) |
## Uploads
`api.upload(path, formData)` is the multipart path. `rawRequest` deliberately
does **not** set `Content-Type` for a `FormData` body — the browser must set it
itself so the multipart boundary is included; setting it by hand makes the
server see an empty upload (a 422 rather than a 202). `fetch` reports no upload
progress, so upload UIs stay indeterminate.
## Deploy
The app is a client-rendered SPA served as static assets by a Cloudflare Worker
(`wrangler.jsonc`, `not_found_handling: single-page-application`). The build
emits `_shell.html`, which is copied to `index.html` so Cloudflare's SPA
fallback returns it for every client route.
**Manual deploy** (needs `wrangler login` or `CLOUDFLARE_API_TOKEN` /
`CLOUDFLARE_ACCOUNT_ID` in your env):
```bash
pnpm cf:deploy # vite build → copy shell to index.html → wrangler deploy
```
**CI/CD** — every push & PR runs typecheck + test + build; pushes to `main`
build and deploy to Cloudflare. Two mirrored pipelines are provided:
| Platform | Workflow |
| --- | --- |
| GitHub Actions | `.github/workflows/ci.yml` |
| Gitea Actions | `.gitea/workflows/ci.yml` |
Configure these on the repo before the first deploy:
| Type | Name | Value |
| --- | --- | --- |
| Variable | `VITE_API_BASE_URL` | backend API origin baked into the build |
| Secret | `CLOUDFLARE_API_TOKEN` | token with "Edit Cloudflare Workers" scope |
| Secret | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account id |
- GitHub: *Settings → Secrets and variables → Actions*.
- Gitea: *Repo → Settings → Actions → Secrets / Variables*. Actions must be
enabled and an `act_runner` registered with the `ubuntu-latest` label (use an
image such as `catthehacker/ubuntu:act-22.04`).
## Architecture notes
- **Auth**: Bearer tokens in localStorage (zustand persist). `src/api/client.ts`
unwraps the `{success, data, message}` envelope, sends `X-Language-Locale`,
and does a single-flight refresh-then-retry on 401
(`POST /api/v1/auth/refresh-token`, `client_machine_name=admin-api-client`).
- **Permissions**: `GET /auth/me` returns `meta.roles` / `meta.permissions`;
nav items and routes gate on them (`super-admin` passes everything).
- **Lists**: `useListPage` + `<DataTable>` bind to the backend's
`pagination {current_page, page_size, …}` shape (`page`/`page_size` params).
- **Generated types** (`types.gen.ts`) are reference-only (`@ts-nocheck`
scribe emits duplicate operationIds); hand-written shapes live in
`src/api/types.ts`.
## Features (milestone 1)
- 分类管理:分类树 CRUD、待审核队列通过/驳回/合并到已有分类)
- 内容工作室:机器人作者(暂停/恢复)、系列、文章、子话题、分类简报、风格预设、
Agent 接入令牌(签发一次性展示 / 吊销)
- 用户与系统SID 保留规则、幸运号码、查询/分配;语言设置;推送设备;机器人会话令牌
Next milestones: moderation reports (comment / file-x), dashboard stats
(needs backend endpoints).