Files
gig-admin/README.md
T
kongkxandClaude Opus 5 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 <[email protected]>
2026-08-24 23:21:15 +08:00

100 lines
4.0 KiB
Markdown

# 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
# → [email protected] / 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).