o2o-site-AEO/solution/frontend/src/api
2026-09-14 19:45:25 +09:00
..
generated Merge branch 'main' into feature/crawler 2026-09-14 19:45:25 +09:00
mutator 이름: solution/front → solution/frontend 2026-08-31 15:27:16 +09:00
index.ts 이름: solution/front → solution/frontend 2026-08-31 15:27:16 +09:00
pollJob.ts 이름: solution/front → solution/frontend 2026-08-31 15:27:16 +09:00
README.md 문서: 앱을 가른 뒤 낡아진 서술을 고치고, 개발과 무관해진 기록을 지운다 2026-08-31 16:58:09 +09:00

api

api/
├── index.ts              ★ 화면이 import 하는 단 하나의 입구
├── generated/            orval 산출물 — 손대지 않는다
├── mutator/custom-fetch.ts  모든 호출이 지나는 길목(토큰·에러·baseURL·434 재발급)
└── pollJob.ts            잡 폴링(수집·비전·생성·빌드 공용)

화면은 @/api 하나만 본다.

import {useListPlaces, useTransitionFact, startBuild, pollJob} from '@/api';
import type {PlaceData, FactStatus} from '@/api';

재생성

백엔드 OpenAPI 가 바뀌면 다시 뽑는다. generated/ 를 손으로 고치지 않는다.

# 레포 루트에서. 백엔드가 떠 있을 때
npm run orval

# 서버 없이 (스펙 파일을 먼저 뽑는다)
cd solution/backend && .venv/bin/python scripts/export_openapi.py
cd ../.. && ORVAL_INPUT=solution/backend/openapi.json npm run orval

생성되는 것 — 태그(도메인)별 훅과 모델.

태그 훅
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
faq useListFaqs useCreateFaq useTransitionFaq
media useListMedia
local-content usePublish useSyncFestivals useUpdateContent useEndContent — 내부 운영 전용

orval.config.ts 의 operationName 이 FastAPI 의 list_places_v1_place_list_get 를 listPlaces 로 되돌린다 — 백엔드는 무수정이다.

규약 세 가지

1. 거절도 HTTP 200 이다. 도메인 거절은 result.success=false + result.desc(ErrorType 이름)로 온다. React Query 는 성공으로 보므로 onSuccess 안에서 직접 봐야 한다. 안 보면 저장 안 된 값이 저장된 것처럼 보인다.

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() 하나뿐이다 — 화면마다 다시 쓰지 않는다.

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 가 읽는다.