# api ``` api/ ├── index.ts ★ 화면이 import 하는 단 하나의 입구 ├── generated/ orval 산출물 — 손대지 않는다 ├── mutator/custom-fetch.ts 모든 호출이 지나는 길목(토큰·에러·baseURL·434 재발급) └── pollJob.ts 잡 폴링(수집·비전·생성·빌드 공용) ``` 화면은 `@/api` 하나만 본다. ```ts import {useListPlaces, useTransitionFact, startBuild, pollJob} from '@/api'; import type {PlaceData, FactStatus} from '@/api'; ``` ## 재생성 백엔드 OpenAPI 가 바뀌면 다시 뽑는다. **`generated/` 를 손으로 고치지 않는다.** ```bash # 백엔드가 떠 있을 때 npm run orval -w admin # 서버 없이 (스펙 파일을 먼저 뽑는다) cd backend && python scripts/export_openapi.py cd ../frontend && ORVAL_INPUT=../../backend/openapi.json npm run orval -w admin ``` 생성되는 것 — 태그(도메인)별 훅과 모델. | 태그 | 훅 | |---|---| | `auth` | `useLogin` `useRefreshToken` `useMe` `useUpdateMe` | | `place` | `useListPlaces` `useCreatePlace` `useGetPlace` `useUpdatePlace` `useVerifyCandidates` `useVerifyPlace` `useListUnits` `useCreateUnit` `useListLinks` `useCreateLink` `useConfirmLink` `useStartCollect` `useStartVision` `useStartCopy` | | `fact` | `useGetSchema` `useListFacts` `useUpsertFact` `useTransitionFact` | | `job` | `useGetJob` `useJobOps` `useRequeueJob` | | `site` | `useGetSite` `useStartBuild` `useListVersions` `useListLogs` `useChangeStatus` | `orval.config.ts` 의 `operationName` 이 FastAPI 의 `list_places_v1_place_list_get` 를 `listPlaces` 로 되돌린다 — 백엔드는 무수정이다. ## 규약 세 가지 **1. 거절도 HTTP 200 이다.** 도메인 거절은 `result.success=false` + `result.desc`(ErrorType 이름)로 온다. React Query 는 성공으로 보므로 `onSuccess` 안에서 직접 봐야 한다. 안 보면 저장 안 된 값이 저장된 것처럼 보인다. ```ts onSuccess: (res) => { if (res.result?.success === false) return notifyApiError({data: res}); ... } ``` 문구 변환은 `@/lib/errorMessages` 한 곳에 있다(`PLACE_NOT_VERIFIED` → "동일 업소 검증을 먼저…"). **2. 응답의 `None` 필드는 키째 사라진다**(백엔드 `RemoveNoneResponse`). 게다가 백엔드가 기본값을 준 필드는 OpenAPI 에서 required 가 아니라 생성 타입이 전부 optional 이다 — `undefined` 를 각오하고 쓴다. **3. 몇 분 걸리는 일은 잡이다.** 수집·비전·생성·빌드는 `job_id` 를 받고 폴링한다. 루프는 `pollJob()` 하나뿐이다 — 화면마다 다시 쓰지 않는다. ```ts const started = await startCollect(placeId, {}); const outcome = await pollJob(started.job_id!, {signal, onTick: (job) => ...}); // outcome.kind: done | dead | timeout | aborted | unreachable ``` ★ `done` 은 "잡이 끝났다"이지 "성공했다"가 아니다. 빌드는 게이트에 막혀도 정상 종료하고 `job.result.gate.passed` 가 false 로 온다 — 판정은 `features/publish/usePublishSite.ts` 가 읽는다.