feat(solution): 발행 워커·버전 관리와 예약·미리보기 정리
상시 프리렌더와 중복 예약 안내를 없애고, 검수된 발행 버전을 보존한다. 미리보기는 실제 렌더 완료까지 스피너를 표시한다. 사이트 81건, 발행·롤백·서치콘솔 45건, 프로세스 수명 3건 통과. 빌더·사이트 빌드 및 compose 설정 검증 통과.
This commit is contained in:
parent
0f7d22750f
commit
f2087aad5e
@ -1,5 +1,8 @@
|
||||
# AGENTS.md — 이 레포에서 작업하기 전에
|
||||
|
||||
> 2026-09-15 발행 버전 전환: [docs/PUBLISH_VERSION.md](docs/PUBLISH_VERSION.md)가
|
||||
> 아래의 상시 프리렌더·재굽기·자산 주소 교체 절차를 대체한다. 목업은 변경하지 않는다.
|
||||
|
||||
에이전트와 신규 합류자가 **먼저 읽는 파일**이다. 여기에는 *밟기 쉬운 함정*과 *규약*만 둔다.
|
||||
설명은 각 문서가 단일 출처다 — 여기로 복사하지 말고 링크한다.
|
||||
|
||||
|
||||
141
deploy.sh
141
deploy.sh
@ -1,127 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# 배포 — 코드를 당기고, 지정한 서비스만 다시 빌드해 갈아끼운다.
|
||||
#
|
||||
# ./deploy.sh 전체
|
||||
# ./deploy.sh solution-backend 그 서비스만
|
||||
# ./deploy.sh solution-backend solution-worker 여럿
|
||||
#
|
||||
# 서비스명 대신 컨테이너명(o2o-web4ai-solution-backend)으로 불러도 받는다.
|
||||
# 서버 변경과 꺼진 admin을 보존한다. 렌더러 배포도 기존 사이트를 재굽지 않는다.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
PREFIX=o2o-web4ai
|
||||
# api·worker·api-admin 은 이미지 한 벌(o2o-web4ai-backend)을 나눠 쓴다.
|
||||
BACKEND_SVCS=(solution-backend solution-worker admin-backend)
|
||||
BRANCH=${DEPLOY_BRANCH:-main}
|
||||
|
||||
PULL=1
|
||||
ONLY=0
|
||||
TARGETS=()
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
사용법: ./deploy.sh [옵션] [서비스...]
|
||||
|
||||
옵션
|
||||
--no-pull 코드를 당기지 않는다(디스크에 있는 코드 그대로 빌드)
|
||||
--only 백엔드 형제 서비스를 함께 갈아끼우지 않는다 (아래 ★ 참고)
|
||||
-h, --help
|
||||
|
||||
환경변수
|
||||
DEPLOY_BRANCH 기본 main. 다른 브랜치를 배포할 때만 쓴다
|
||||
|
||||
서비스: solution-backend · solution-worker · solution-prerender · solution-site
|
||||
admin-backend · admin-frontend (프로필 admin, 기본 기동에서 빠져 있다)
|
||||
solution-frontend (프로필 dev, 로컬 HMR 전용 — 운영에서 띄우지 않는다)
|
||||
o2o-web4ai-solution-backend 처럼 컨테이너명으로 적어도 된다
|
||||
|
||||
★ solution-backend·solution-worker·admin-backend 는 이미지가 한 벌이다. 하나를 빌드하면 나머지도 새 이미지로
|
||||
갈아끼워야 한다 — 안 그러면 옛 코드로 도는 컨테이너가 남는데, 셋 다 "살아 있음" 이라
|
||||
화면상으로는 배포가 끝난 것처럼 보인다. --only 는 그걸 알고 건너뛸 때만 쓴다.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-pull) PULL=0 ;;
|
||||
--only) ONLY=1 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-*) echo "모르는 옵션: $1" >&2; usage >&2; exit 2 ;;
|
||||
*) TARGETS+=("${1#"$PREFIX"-}") ;; # 컨테이너명으로 불러도 받는다
|
||||
--only) ONLY=1 ;;
|
||||
-h|--help) echo "사용법: ./deploy.sh [--no-pull] [--only] [서비스...]"; exit 0 ;;
|
||||
-*) echo "모르는 옵션: $arg" >&2; exit 2 ;;
|
||||
*) TARGETS+=("${arg#o2o-web4ai-}") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
ALL_SVCS=$(docker compose config --services)
|
||||
for t in ${TARGETS+"${TARGETS[@]}"}; do
|
||||
grep -qx "$t" <<<"$ALL_SVCS" || {
|
||||
echo "그런 서비스가 없다: $t" >&2
|
||||
echo "있는 것: $(tr '\n' ' ' <<<"$ALL_SVCS")" >&2
|
||||
exit 2
|
||||
}
|
||||
done
|
||||
|
||||
# ── 코드 ────────────────────────────────────────────────────────────
|
||||
# 배포 서버의 작업트리는 main 의 **사본**이지 작업 공간이 아니다. 그래서 머지(pull)가 아니라
|
||||
# origin/$BRANCH 로 하드 리셋한다 — 밖에서 force-push 가 나도 --ff-only 로 막히지 않고,
|
||||
# 서버에서 누가 손댄 흔적이 다음 배포까지 살아남지 않는다.
|
||||
#
|
||||
# ★ fetch 가 실패하면 **리셋하지 않는다.** 이 서버엔 gitea 자격증명이 없어 fetch 가 죽는데,
|
||||
# 그 상태의 origin/$BRANCH 는 마지막으로 fetch 된 낡은 ref 다.
|
||||
# 실측(2026-09-01 킹서버): HEAD=9b4fe40 인데 origin/main=4871e50 — 믿고 리셋하면 한 커밋
|
||||
# 롤백된다. 그러고도 빌드는 성공하고 컨테이너는 뜬다. 조용히 틀리는 종류다.
|
||||
#
|
||||
# ★ git clean 은 하지 않는다. .env 와 nginx/site.conf 는 추적되지 않는 파일이고 서버마다
|
||||
# 다르다 — reset --hard 는 이 둘을 건드리지 않지만 clean 은 지운다.
|
||||
if [ "$PULL" = 1 ]; then
|
||||
echo "▶ git fetch origin $BRANCH"
|
||||
if git fetch --prune origin "$BRANCH" 2>&1; then
|
||||
DIRTY=$(git status --porcelain)
|
||||
if [ -n "$DIRTY" ]; then
|
||||
echo " ! 작업트리 변경을 버린다:"
|
||||
sed 's/^/ /' <<<"$DIRTY"
|
||||
fi
|
||||
echo "▶ git reset --hard origin/$BRANCH"
|
||||
git reset --hard "origin/$BRANCH"
|
||||
else
|
||||
echo " ! fetch 실패 — 리셋을 건너뛰고 디스크에 있는 코드 그대로 간다." >&2
|
||||
echo " origin/$BRANCH 가 낡았을 수 있어 그걸로 리셋하면 배포가 조용히 롤백된다." >&2
|
||||
echo " (밖에서 밀어넣었으면 이대로 두면 되고, 자동화하려면 gitea 배포키를 건다)" >&2
|
||||
fi
|
||||
git diff --quiet && git diff --cached --quiet || { echo "서버 변경을 먼저 정리하세요" >&2; exit 1; }
|
||||
git pull --ff-only origin "${DEPLOY_BRANCH:-main}"
|
||||
fi
|
||||
echo "▶ 지금 코드: $(git log --oneline -1)"
|
||||
|
||||
# ── 대상 정하기 ─────────────────────────────────────────────────────
|
||||
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||
SVCS=() # 빈 인자 = compose 가 전부로 해석한다
|
||||
echo "▶ 대상: 전체"
|
||||
else
|
||||
SVCS=("${TARGETS[@]}")
|
||||
# 백엔드 하나를 건드리면 같은 이미지를 쓰는 형제도 함께 갈아끼운다.
|
||||
if [ "$ONLY" = 0 ]; then
|
||||
for t in "${TARGETS[@]}"; do
|
||||
for b in "${BACKEND_SVCS[@]}"; do [ "$t" = "$b" ] || continue
|
||||
for sib in "${BACKEND_SVCS[@]}"; do
|
||||
printf '%s\n' "${SVCS[@]}" | grep -qx "$sib" || {
|
||||
SVCS+=("$sib")
|
||||
echo " + $sib — 같은 이미지를 쓴다(옛 코드로 남지 않게 함께 간다)"
|
||||
}
|
||||
done
|
||||
done
|
||||
done
|
||||
fi
|
||||
echo "▶ 대상: ${SVCS[*]}"
|
||||
[ ${#TARGETS[@]} -gt 0 ] || TARGETS=(solution-backend solution-worker solution-site)
|
||||
RUNNING=$(docker compose ps --services --status running)
|
||||
if [ "$ONLY" = 0 ]; then
|
||||
case " ${TARGETS[*]} " in
|
||||
*" solution-backend "*|*" solution-worker "*)
|
||||
TARGETS+=(solution-backend solution-worker)
|
||||
if grep -qx admin-backend <<<"$RUNNING"; then TARGETS+=(admin-backend); fi ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ── 빌드 · 교체 ─────────────────────────────────────────────────────
|
||||
# build 절이 없는 서비스(node:24-alpine · nginx:alpine)는 build 가 조용히 건너뛴다.
|
||||
echo "▶ build"
|
||||
docker compose build ${SVCS+"${SVCS[@]}"}
|
||||
|
||||
echo "▶ up -d"
|
||||
docker compose up -d --force-recreate ${SVCS+"${SVCS[@]}"}
|
||||
|
||||
echo
|
||||
SERVICES=()
|
||||
for target in "${TARGETS[@]}"; do
|
||||
case " ${SERVICES[*]-} " in *" $target "*) ;; *) SERVICES+=("$target");; esac
|
||||
done
|
||||
# site의 렌더러도 worker 이미지에 들어간다.
|
||||
case " ${SERVICES[*]} " in
|
||||
*" solution-site "*) case " ${SERVICES[*]} " in *" solution-worker "*) ;; *) SERVICES+=(solution-worker);; esac ;;
|
||||
esac
|
||||
docker compose build "${SERVICES[@]}"
|
||||
docker compose up -d --no-deps --force-recreate "${SERVICES[@]}"
|
||||
docker compose ps
|
||||
echo
|
||||
echo "로그: ./log.sh"
|
||||
|
||||
@ -28,8 +28,12 @@ x-common-env: &common-env
|
||||
# ★ 기본값은 localhost 다. 운영 도메인을 기본으로 두면 .env 를 안 채운 로컬 빌드가
|
||||
# 조용히 운영 주소를 번들에 굽는다(실측 2026-09-03: 로컬 링크가 킹서버로 갔다).
|
||||
SITE_PUBLIC_HOST: ${SITE_PUBLIC_HOST:-localhost}
|
||||
SITE_PAYLOAD_DIR: /app/out/payloads
|
||||
SITE_OUTPUT_DIR: /app/out/sites
|
||||
# ★ 렌더러(solution/site/scripts/prerender.ts)가 songs·out 디렉토리를 **자기 파일 위치
|
||||
# 기준 상대경로**로 찾는다(SITE_ROOT = dist/prerender/../..) — 워커 컨테이너 안에서 그
|
||||
# 렌더러를 직접 띄우므로(render_service.py) 세 디렉토리가 실제 설치 자리
|
||||
# (/app/solution/site/) 아래 나란히 있어야 한다. 아래 볼륨 마운트와 짝이 맞아야 한다.
|
||||
SITE_PAYLOAD_DIR: /app/solution/site/payloads
|
||||
SITE_OUTPUT_DIR: /app/solution/site/out
|
||||
# ★ 프리렌더와 같은 값이어야 한다. 어긋나면 색인 통보가 403 이다.
|
||||
INDEXNOW_KEY: ${INDEXNOW_KEY:-}
|
||||
|
||||
@ -48,10 +52,10 @@ services:
|
||||
# ★ 크론은 이 컨테이너에서만 돈다. 프로세스가 여럿이면 같은 시각에 중복 실행된다.
|
||||
SCHEDULER_ENABLED: "1"
|
||||
volumes:
|
||||
- ./solution/site/payloads:/app/out/payloads
|
||||
# 노래 파일. payload 와 나란히 둔다 — 백엔드가 mp3 를 여기 떨구면 프리렌더가
|
||||
# 사이트 디렉토리로 복사한다(services/song_service · site/scripts/prerender.ts).
|
||||
- ./solution/site/songs:/app/out/songs
|
||||
- ./solution/site/payloads:/app/solution/site/payloads
|
||||
# 노래 파일. payload 와 나란히 둔다 — 백엔드가 mp3 를 여기 떨구면 워커가 렌더러를
|
||||
# 돌릴 때 사이트 디렉토리로 복사된다(services/song_service · site/scripts/prerender.ts).
|
||||
- ./solution/site/songs:/app/solution/site/songs
|
||||
# ★ 스키마 마이그레이션 SQL. 이미지에 굽지 않고 마운트한다 — 파일이 자주 늘고,
|
||||
# 이미 세운 DB 를 따라오게 하는 것이 목적이라 코드 배포와 별개로 돌 수 있어야 한다.
|
||||
- ./postgres-init:/app/postgres-init:ro
|
||||
@ -64,13 +68,17 @@ services:
|
||||
driver: json-file
|
||||
options: { max-size: "10m", max-file: "5" }
|
||||
|
||||
# ★ BUILD·ROLLBACK 잡이 렌더러(solution/site)를 subprocess 로 직접 돌린다
|
||||
# (services/render_service.py) — 예전에 solution-prerender 컨테이너가 하던 일이다.
|
||||
# 그래서 이 서비스만 **다른 이미지**(Dockerfile.worker, Node 런타임 + 컴파일된 렌더러
|
||||
# 포함)를 쓴다. solution-backend·admin-backend 는 그 Node 를 쓸 일이 없다.
|
||||
solution-worker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: solution/backend/Dockerfile
|
||||
image: o2o-web4ai-backend
|
||||
dockerfile: solution/backend/Dockerfile.worker
|
||||
image: o2o-web4ai-worker
|
||||
container_name: o2o-web4ai-solution-worker
|
||||
command: ["python", "worker_main.py"]
|
||||
command: ["sh", "-c", "node /app/solution/site/dist/prerender/prerender.js --seed-assets --out=/app/solution/site/out && exec python worker_main.py"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@ -79,15 +87,20 @@ services:
|
||||
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-1}
|
||||
JOB_DEADLINE_SEC: ${JOB_DEADLINE_SEC:-900}
|
||||
JOB_LEASE_SEC: ${JOB_LEASE_SEC:-120}
|
||||
# 렌더러 subprocess 가 굽는 동안 기다리는 시간(사진 내려받기 포함). BUILD 의
|
||||
# job_deadline_sec 보다 짧아야 한다 — 안 그러면 잡 전체가 먼저 타임아웃된다.
|
||||
RENDER_TIMEOUT_SEC: ${RENDER_TIMEOUT_SEC:-180}
|
||||
# 이미지의 HEALTHCHECK 는 API 용(HTTP :9800)이다. 워커는 포트가 없어 그대로 두면 늘 unhealthy 다.
|
||||
healthcheck:
|
||||
disable: true
|
||||
volumes:
|
||||
- ./solution/site/payloads:/app/out/payloads
|
||||
# 노래 파일. payload 와 나란히 둔다 — 백엔드가 mp3 를 여기 떨구면 프리렌더가
|
||||
- ./solution/site/payloads:/app/solution/site/payloads
|
||||
# 노래 파일. payload 와 나란히 둔다 — 백엔드가 mp3 를 여기 떨구면 렌더러가
|
||||
# 사이트 디렉토리로 복사한다(services/song_service · site/scripts/prerender.ts).
|
||||
- ./solution/site/songs:/app/out/songs
|
||||
- site-out:/app/out/sites:ro
|
||||
- ./solution/site/songs:/app/solution/site/songs
|
||||
# ★ 이제 이 컨테이너가 굽는 쪽이다 — 읽기전용이 아니다(예전 solution-prerender 가
|
||||
# 쓰던 자리를 그대로 이어받는다).
|
||||
- site-out:/app/solution/site/out
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
stop_grace_period: 300s
|
||||
@ -124,10 +137,10 @@ services:
|
||||
start_period: 20s
|
||||
retries: 3
|
||||
volumes:
|
||||
- ./solution/site/payloads:/app/out/payloads
|
||||
- ./solution/site/payloads:/app/solution/site/payloads
|
||||
# 노래 파일. payload 와 나란히 둔다 — 백엔드가 mp3 를 여기 떨구면 프리렌더가
|
||||
# 사이트 디렉토리로 복사한다(services/song_service · site/scripts/prerender.ts).
|
||||
- ./solution/site/songs:/app/out/songs
|
||||
- ./solution/site/songs:/app/solution/site/songs
|
||||
ports:
|
||||
# ★ 내부망에만 연다. 0.0.0.0 으로 열면 API 를 가른 의미가 없다.
|
||||
- "${ADMIN_API_BIND:-127.0.0.1}:${ADMIN_API_PORT_PUBLIC:-9801}:9801"
|
||||
@ -140,48 +153,6 @@ services:
|
||||
driver: json-file
|
||||
options: { max-size: "10m", max-file: "5" }
|
||||
|
||||
# 발행 사이트를 굽는다. **굽기만 한다** — 서빙은 solution-site(nginx)가 맡는다.
|
||||
#
|
||||
# ★ 사장님 앱 dev 서버는 여기서 빠졌다. 운영에 `vite dev` 를 띄우면 요청마다 트랜스파일하고
|
||||
# 기동이 npm install 네트워크에 의존하고 /src 원본이 그대로 나간다. 번들은 solution-site
|
||||
# 이미지가 굽는다(nginx/Dockerfile). 로컬에서 HMR 이 필요하면 `--profile dev`.
|
||||
solution-prerender:
|
||||
image: node:24-alpine
|
||||
container_name: o2o-web4ai-solution-prerender
|
||||
working_dir: /app
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
cd /app
|
||||
# ★ `-d node_modules` 로 판단하면 안 된다. 익명 볼륨은 빈 디렉토리로 이미 존재해서
|
||||
# 설치를 건너뛰고 `vite: not found`(exit 127)로 죽는다.
|
||||
[ -x node_modules/.bin/vite ] || npm install
|
||||
exec node solution/site/scripts/watch-payloads.mjs
|
||||
environment:
|
||||
INDEXNOW_KEY: ${INDEXNOW_KEY:-}
|
||||
# ★ 발행 호스트를 프론트 .env 에 따로 적지 않는다 — 루트 .env 의 SITE_PUBLIC_HOST 를
|
||||
# 그대로 흘려보낸다. 두 곳에 적으면 canonical 과 화면 주소가 조용히 갈라진다.
|
||||
VITE_PUBLISH_HOST: ${SITE_PUBLIC_HOST:-localhost}
|
||||
volumes:
|
||||
- ./package.json:/app/package.json
|
||||
- ./package-lock.json:/app/package-lock.json
|
||||
- ./tsconfig.base.json:/app/tsconfig.base.json
|
||||
- ./solution:/app/solution
|
||||
- ./admin:/app/admin
|
||||
# ★ node_modules 는 컨테이너 것을 쓴다. 호스트가 macOS(arm64-darwin)라 그 안의
|
||||
# rollup·esbuild 네이티브 바이너리를 리눅스 컨테이너가 못 쓴다.
|
||||
- /app/node_modules
|
||||
- /app/solution/site/node_modules
|
||||
- /app/solution/frontend/node_modules
|
||||
- /app/admin/frontend/node_modules
|
||||
# ★ 산출물은 named volume. 호스트 경로면 재배포로 코드를 갈아엎는 순간 전 사이트가 404 다.
|
||||
- site-out:/app/solution/site/out
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options: { max-size: "10m", max-file: "5" }
|
||||
|
||||
# 사장님 앱 dev 서버 + 발행본 정적서버(:3001). **로컬 전용**이다 — `--profile dev`.
|
||||
# 운영에서 이게 뜨면 안 된다(위 solution-prerender 주석).
|
||||
solution-frontend:
|
||||
@ -294,7 +265,7 @@ services:
|
||||
# 앞단 프록시를 세울 거면 여기서 포트만 옮기고 프록시가 이쪽을 가리키게 한다.
|
||||
- "${SITE_HTTP_BIND:-0.0.0.0}:${SITE_HTTP_PORT:-80}:80"
|
||||
depends_on:
|
||||
- solution-prerender
|
||||
- solution-backend
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
# ARCHITECTURE
|
||||
|
||||
> 2026-09-15: 현재 발행 실행·산출물 버전·배포는 [PUBLISH_VERSION.md](PUBLISH_VERSION.md).
|
||||
> 아래의 별도 프리렌더 컨테이너·폴링·전체 재굽기 설명은 이전 구조다.
|
||||
|
||||
제품 판단은 [PRODUCT.md](PRODUCT.md), 배포 절차는 [DEPLOY.md](DEPLOY.md),
|
||||
에이전트가 밟기 쉬운 함정 목록은 [AGENTS.md](../AGENTS.md). 여기는 **구조와 경계**만 다룬다.
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
# 배포 · 스토리지
|
||||
|
||||
> 2026-09-15 이후 절차는 [PUBLISH_VERSION.md](PUBLISH_VERSION.md)를 따른다.
|
||||
> 기존 사이트 전체 재굽기는 하지 않는다. 최초 전환 때 구 프리렌더를 중지한다.
|
||||
|
||||
> **현재 결정 (2026-08-31): 발행 사이트는 서버 안에서 nginx 가 정적 서빙한다.**
|
||||
> Azure Blob 은 코드에 있으나 **켜지 않는다**(`AZURE_STORAGE_CONNECTION_STRING` 비움).
|
||||
> 클라우드는 고도화 때 붙인다 — 근거는 [ARCHITECTURE.md 3절](ARCHITECTURE.md).
|
||||
|
||||
@ -1,5 +1,15 @@
|
||||
# 개발 일지
|
||||
|
||||
## 2026-09-15 — 워커 렌더·발행 버전·예약 안내·미리보기 대기
|
||||
|
||||
- 상시 프리렌더를 제거하고 워커가 컴파일된 Node 렌더러를 실행한다.
|
||||
- 버전별 HTML을 보존하고 게이트 통과 뒤 공개 링크를 전환한다. 재시도는 저장된 성공본을 사용한다.
|
||||
- 예약 전 확인을 이용안내에 통합하고 iframe 렌더 완료까지 스피너를 표시한다.
|
||||
- 배포는 기존 HTML과 목업을 재굽지 않는다. 상세: [PUBLISH_VERSION.md](PUBLISH_VERSION.md).
|
||||
- 읽기 생성 토큰 상한을 늘리고 추첨 배열을 고정해 반복 렌더를 방지한다.
|
||||
- 편집기 주소는 /builder?placeId=…로 통일한다. 옛 step=editor 주소는 ID 복원 후 정정한다.
|
||||
- 검증: 사이트 81건, 백엔드 발행·롤백·서치콘솔 45건 통과. 빌더·사이트 빌드 통과.
|
||||
|
||||
무엇을 왜 바꿨는지 날짜순으로 남긴다. 새 항목을 **위에** 추가한다.
|
||||
결론과 배경은 각 문서가 단일 출처다 — 여기에는 요약과 링크만 둔다.
|
||||
|
||||
|
||||
32
docs/PUBLISH_VERSION.md
Normal file
32
docs/PUBLISH_VERSION.md
Normal file
@ -0,0 +1,32 @@
|
||||
# 발행 버전과 워커 (2026-09-15)
|
||||
|
||||
이 문서가 이전 문서의 프리렌더 상시 기동·전체 재굽기 절차를 대체한다.
|
||||
|
||||
`BUILD → snapshot → payload → Node 렌더 → 결과 게이트 → 공개 링크 전환 → DB 기록`
|
||||
|
||||
- Python은 HTML을 만들지 않는다. 미리 컴파일된 Node를 실행하고 JSON 보고서만 읽는다.
|
||||
- 워커 이미지에 Node와 렌더러를 포함한다. 실행 중 npm 설치·번들 빌드는 없다.
|
||||
- 공유 볼륨의 파일 잠금으로 렌더·공개 전환을 직렬화한다. 수집 등 다른 잡은 동시 실행한다.
|
||||
- `out/versions/<slug>/<version>`에 성공한 HTML과 렌더 보고서를 보존한다.
|
||||
- `out/s/<slug>`는 공개 버전의 상대 심볼릭 링크다. 게이트 통과 후 전환한다.
|
||||
- 성공한 버전은 재시도·롤백 때 다시 쓰지 않는다. 기존 일반 디렉토리는 첫 재발행 때 legacy로 보존한다.
|
||||
- 배포 시 HTML·기존 HTML의 자산 주소를 수정하지 않는다. 공용 자산과 미리보기 셸만 준비한다.
|
||||
- 목업과 보관 버전의 참조 자산도 삭제 대상에서 제외한다. versions는 Azure 공용 업로드에서 제외한다.
|
||||
- 롤백 API: `POST /v1/place/{place_id}/site/version/rollback`, `target_version`. 소유권 검사와 BUILD 중복 방지 키를 공유한다.
|
||||
- 로컬 공개 전환과 DB/Azure는 단일 트랜잭션이 아니다. 외부 저장소·DB 실패 시 재시도 및 운영 확인이 필요하다.
|
||||
- 최초 일반 디렉토리→링크 전환은 두 rename 사이 짧은 공백이 가능하다. 이후 링크 교체는 원자적이다.
|
||||
|
||||
## 배포
|
||||
|
||||
1. 진행 중 잡·서버 변경·목업 및 참조 자산 해시를 확인하고 site-out을 백업한다.
|
||||
2. backend·worker·site 이미지를 빌드한다. admin은 기본 대상이 아니다.
|
||||
3. 기존 worker와 solution-prerender를 중지한 뒤 새 worker를 기동한다. 두 렌더러를 동시에 실행하지 않는다.
|
||||
4. API·미리보기·테스트 발행을 확인하고 목업 해시를 대조한다. 전체 재굽기·republish_all은 실행하지 않는다.
|
||||
|
||||
워커 경로: `SITE_PAYLOAD_DIR=/app/solution/site/payloads`, `SITE_OUTPUT_DIR=/app/solution/site/out`.
|
||||
DB 테이블 추가는 없다. 기존 버전·잡·발행 로그를 사용한다.
|
||||
|
||||
## UI
|
||||
|
||||
예약 전 확인과 요약을 이용안내 및 예약에 통합한다. 별도 요약 섹션과 예약 카드의 중복 규정은 제거한다.
|
||||
빌더 미리보기는 iframe 내부 렌더 완료 신호까지 스피너를 표시한다. 출처·iframe을 확인하고 12초 상한을 둔다.
|
||||
@ -93,7 +93,7 @@ CREATE TABLE IF NOT EXISTS public.users (
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS public.jobs (
|
||||
job_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- ★ 이 표만 server_default 가 꼭 필요하다 — 큐 전이가 raw SQL(RETURNING)이라 ORM 의 파이썬 default 가 안 먹는다
|
||||
job_type SMALLINT NOT NULL, -- JobType: 1=collect 2=vision 3=copy 4=build 5=local_sync 6=ai_check
|
||||
job_type SMALLINT NOT NULL, -- JobType: 1=collect 2=vision 3=copy 4=build 5=local_sync 6=ai_check 7=song 8=rollback
|
||||
status SMALLINT NOT NULL DEFAULT 1, -- JobStatus: 1=pending 2=running 3=done 4=dead
|
||||
priority SMALLINT NOT NULL DEFAULT 100, -- 낮을수록 우선
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
|
||||
70
solution/backend/Dockerfile.worker
Normal file
70
solution/backend/Dockerfile.worker
Normal file
@ -0,0 +1,70 @@
|
||||
# 워커 전용 이미지 — solution-backend(:9800)·admin-backend(:9801) 와 **다른 이미지**다.
|
||||
#
|
||||
# ★ 왜 갈랐나 (프리렌더를 워커 실행으로 통합, 2026-09-15)
|
||||
# BUILD·ROLLBACK 잡은 이제 컴파일된 Node 렌더러(solution/site)를 subprocess 로 직접
|
||||
# 돌린다(services/render_service.py) — 예전에 별도 컨테이너(solution-prerender)가 하던
|
||||
# 일이다. 그 렌더러가 필요한 건 **워커뿐**이다. Dockerfile(api/admin 겸용)에 Node 를
|
||||
# 같이 넣으면 API 를 여는 요청만 받는 컨테이너에도 쓰지 않는 Node 런타임과 렌더러
|
||||
# 번들이 실려 나간다 — 이미지가 커지고 공격 표면도 는다.
|
||||
#
|
||||
# ★ 기동·잡 실행 중에 npm install 을 하지 않는다. site-builder 스테이지가 이미지 빌드
|
||||
# 시점에 한 번 굽고, 최종 이미지는 컴파일된 산출물만 COPY 한다.
|
||||
# ★ node_modules 는 안 들어간다 — solution/site/vite.config.ts 가 SSR 번들에
|
||||
# `ssr.noExternal: true` 로 react 등 의존성까지 전부 접어 넣는다(실측: node_modules 를
|
||||
# 지우고 실행해도 그대로 돈다, dist/prerender/prerender.js 단일 파일 1.8MB).
|
||||
#
|
||||
# ★ 빌드 컨텍스트는 solution-backend 와 같은 **레포 루트**다 — solution/site 와
|
||||
# solution/backend 를 한 이미지에 담아야 한다.
|
||||
|
||||
# ── 1단계: 렌더러 컴파일 (Node) ──────────────────────────────────────────────
|
||||
FROM node:24-bookworm AS site-builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json tsconfig.base.json ./
|
||||
COPY solution/shared/package.json solution/shared/
|
||||
COPY solution/frontend/package.json solution/frontend/
|
||||
COPY solution/site/package.json solution/site/
|
||||
COPY admin/frontend/package.json admin/frontend/
|
||||
# ★ npm workspaces 는 루트 package.json 의 workspaces 목록에 있는 자리마다 package.json 을
|
||||
# 요구한다 — site 만 빌드해도 4개를 전부 COPY 해야 npm ci 가 성공한다(nginx/Dockerfile 과 같다).
|
||||
RUN npm ci
|
||||
|
||||
COPY solution/shared ./solution/shared
|
||||
COPY solution/site ./solution/site
|
||||
RUN npm run build:client -w @o2o/site \
|
||||
&& npm run build:prerender -w @o2o/site
|
||||
|
||||
# ── 2단계: Node 런타임만 (바이너리를 뽑아 쓴다) ──────────────────────────────
|
||||
# ★ python:3.12-slim 과 같은 계열(Debian bookworm)의 태그를 써야 한다 — glibc ABI 가
|
||||
# 어긋나면 바이너리만 복사해서는 못 돈다("cannot execute binary file" 류로 죽는다).
|
||||
FROM node:24-bookworm-slim AS node-runtime
|
||||
|
||||
# ── 3단계: 최종 워커 이미지 ───────────────────────────────────────────────
|
||||
FROM python:3.12-slim-bookworm
|
||||
WORKDIR /app
|
||||
|
||||
COPY solution/backend/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
# ★ yanolja_adapter.py 가 Playwright 로 페이지를 렌더링한다(수집 파이프라인, 워커 전용 경로) —
|
||||
# 패키지(pip)만으로는 브라우저 실행 파일이 없다. --with-deps 가 Chromium 이 필요로 하는
|
||||
# 시스템 라이브러리(libnss3 등)까지 apt 로 같이 깐다.
|
||||
RUN playwright install --with-deps chromium
|
||||
|
||||
# Node 바이너리만 옮겨 온다 — npm·corepack 은 쓰지 않는다(빌드는 site-builder 스테이지가
|
||||
# 이미 끝냈다). /usr/local/bin/node 하나로 `node dist/prerender/prerender.js` 를 돌리는
|
||||
# 데 충분하다(실측: 아래 COPY 만으로 `node --version` 과 렌더 스모크 테스트가 통과한다).
|
||||
COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node
|
||||
|
||||
COPY solution/backend ./solution/backend
|
||||
# ★ SITE_ROOT(solution/site/scripts/prerender.ts)가 자기 파일 위치 기준 상대경로로
|
||||
# payloads·songs·out 을 찾는다 — dist·public 이 이 자리(/app/solution/site/)에 있어야
|
||||
# 워커가 컨테이너 안에서 렌더러를 그대로 실행할 수 있다.
|
||||
COPY --from=site-builder /app/solution/site/dist ./solution/site/dist
|
||||
COPY --from=site-builder /app/solution/site/public ./solution/site/public
|
||||
|
||||
ENV APP_ENV=local
|
||||
ENV PYTHONPATH=/app/solution/backend
|
||||
WORKDIR /app/solution/backend
|
||||
|
||||
# 워커는 포트가 없다 — 이미지 공통 HEALTHCHECK 를 두지 않는다(compose 가 disable 한다).
|
||||
CMD ["python", "worker_main.py"]
|
||||
@ -389,6 +389,7 @@ class PublishAction(CodeEnum):
|
||||
REBUILD = 3
|
||||
SUSPEND = 4
|
||||
RESUME = 5
|
||||
ROLLBACK = 6 # 예전 버전으로 공개 주소를 되돌림 — services/rollback_service.py
|
||||
|
||||
|
||||
class PublishResult(CodeEnum):
|
||||
@ -434,6 +435,7 @@ class JobType(CodeEnum):
|
||||
LOCAL_SYNC = 5 # 지역 정보 갱신 — 행정구역 코드 단위(같은 지역 사이트 50개여도 1회)
|
||||
AI_CHECK = 6 # AI 검색 노출 점검
|
||||
SONG = 7 # 이 숙소의 노래 한 곡 (가사 Gemini → 작곡 Suno). 발행이 이 잡을 건다
|
||||
ROLLBACK = 8 # 예전 버전 스냅샷으로 다시 굽고 공개 주소를 그 버전으로 되돌림
|
||||
|
||||
|
||||
class JobStatus(CodeEnum):
|
||||
|
||||
@ -166,7 +166,7 @@ def fake_renderer(monkeypatch, tmp_path_factory):
|
||||
|
||||
★ 왜 필요한가
|
||||
발행 게이트는 이제 **실제로 나갈 HTML** 을 보고 판정한다. 그 HTML 은 Node 렌더러가
|
||||
굽고, BUILD 잡은 그 결과 보고서를 기다린다(services/render_report.wait_for).
|
||||
굽고, BUILD 잡은 렌더러 subprocess 를 직접 돌린다(services/render_service.render_site).
|
||||
파이썬 테스트 환경에는 Node 렌더러가 없으므로, payload 를 읽어 보고서를 만들어 주는
|
||||
대역을 끼운다 — 여기서 검사하려는 건 **백엔드가 보고서를 어떻게 처리하는가** 다.
|
||||
|
||||
@ -175,8 +175,9 @@ def fake_renderer(monkeypatch, tmp_path_factory):
|
||||
두 벌로 두면 어긋나고, 어긋난 걸 아무도 모르는 게 원래 문제였다.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from services import render_report, site_payload
|
||||
from services import render_report, render_service, site_payload
|
||||
|
||||
payload_dir = tmp_path_factory.mktemp("payloads")
|
||||
monkeypatch.setattr(site_payload, "payload_dir", lambda: payload_dir)
|
||||
@ -229,13 +230,16 @@ def fake_renderer(monkeypatch, tmp_path_factory):
|
||||
count += sum(1 for m in payload.get("media") or [] if long(m.get("alt")))
|
||||
return count
|
||||
|
||||
async def _wait_for(slug, site_version, timeout_sec, poll_sec=1.0):
|
||||
path = payload_dir / f"{slug}.json"
|
||||
async def _render_site(payload_path, site_version, timeout_sec):
|
||||
path = Path(payload_path)
|
||||
slug = path.stem
|
||||
if not path.is_file():
|
||||
return None
|
||||
raise render_service.RenderFailed(f"payload 파일이 없다: {payload_path}")
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
if payload["site"]["version"] != site_version:
|
||||
return None
|
||||
raise render_service.RenderFailed(
|
||||
f"버전이 다르다(payload={payload['site']['version']} 요청={site_version})"
|
||||
)
|
||||
place = payload.get("place") or {}
|
||||
count = _count(payload)
|
||||
# ★ 고유 콘텐츠 0건이면 렌더러는 **페이지를 쓰지 않는다**
|
||||
@ -276,5 +280,8 @@ def fake_renderer(monkeypatch, tmp_path_factory):
|
||||
"error": None,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(render_report, "wait_for", _wait_for)
|
||||
monkeypatch.setattr(render_service, "render_site", _render_site)
|
||||
async def _activate_site(slug, site_version):
|
||||
return None
|
||||
monkeypatch.setattr(render_service, "activate_site", _activate_site)
|
||||
return payload_dir
|
||||
|
||||
@ -45,6 +45,10 @@ class ISiteCRUD(ABC):
|
||||
async def get_version(self, cdb: AsyncSession, site_id, site_version_id) -> Tuple[ErrorType, site_versions]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_version_by_number(self, cdb: AsyncSession, site_id, version: int) -> Tuple[ErrorType, site_versions]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def finish_version(self, cdb: AsyncSession, site_version_id, data: dict) -> Tuple[ErrorType, int]:
|
||||
pass
|
||||
@ -189,6 +193,31 @@ class SiteCRUD(ISiteCRUD):
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def get_version_by_number(self, cdb: AsyncSession, site_id, version: int) -> Tuple[ErrorType, site_versions]:
|
||||
"""롤백 대상 조회 — site_versions.version(사람이 보는 번호) 로 찾는다.
|
||||
|
||||
★ site_version_id(uuid) 가 아니다. 화면·API 는 버전 번호로 고르는 게 자연스럽고,
|
||||
그 번호가 곧 out/versions/<slug>/<version>/ 디렉토리 이름이다(prerender.ts)."""
|
||||
try:
|
||||
query = (
|
||||
select(site_versions)
|
||||
.where(
|
||||
site_versions.site_id == site_id,
|
||||
site_versions.version == version,
|
||||
site_versions.deleted == False, # noqa: E712
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
err_type, rows = await DB_SESSION_MNG.execute(cdb, query)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
return err_type, None
|
||||
if len(rows) != 1:
|
||||
return ErrorType.DB_INVALID_KEY, None
|
||||
return ErrorType.SUCCESS, rows[0]
|
||||
except Exception as ex:
|
||||
LOG.e_no_callstack(ex)
|
||||
return ErrorType.DB_RUN_FAILED, None
|
||||
|
||||
async def finish_version(self, cdb: AsyncSession, site_version_id, data: dict) -> Tuple[ErrorType, int]:
|
||||
"""빌드 결과를 버전에 기록한다(BUILT 또는 FAILED)."""
|
||||
try:
|
||||
|
||||
@ -36,6 +36,17 @@ class Res_StartBuild(Res_WebPacketProtocol):
|
||||
created: bool = True
|
||||
|
||||
|
||||
class Req_Rollback(SiteProtocol):
|
||||
"""예전 버전으로 공개 주소를 되돌린다.
|
||||
|
||||
★ 재굽기가 아니다 — 대상 버전이 디스크에 아직 있으면 심볼릭 링크만 돌린다. 지워졌으면
|
||||
(보관 정책, prerender.ts pruneOldVersions) site_versions.snapshot 으로 다시 굽고 나서
|
||||
돌린다. 어느 경우든 게이트를 다시 통과해야 한다(사장님이 이미 확인한 값이라 대개는
|
||||
그대로 통과한다)."""
|
||||
|
||||
target_version: int
|
||||
|
||||
|
||||
class SiteVersionData(WebPacketProtocol):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ from common.models.gmodel import PageParams, UserInfo
|
||||
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
|
||||
from services.site_service import SiteService
|
||||
from .protocol import (
|
||||
Req_Rollback,
|
||||
Req_SiteSlug,
|
||||
Req_SiteStatus,
|
||||
Req_SiteTemplate,
|
||||
@ -178,6 +179,20 @@ async def site_preview(
|
||||
return JSONResponse(content=payload)
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/version/rollback",
|
||||
response_model=Res_StartBuild,
|
||||
summary="예전 버전으로 되돌리기(비동기)",
|
||||
description="target_version 을 다시 공개 주소로 내보낸다. 재수집·재생성을 하지 않는다 — "
|
||||
"그 버전이 발행될 때 박제된 snapshot 을 그대로 다시 굽는다. 렌더러가 그 사이 "
|
||||
"바뀌었을 수 있어 게이트는 다시 통과해야 한다. 실패하면 지금 공개된 버전은 그대로다.",
|
||||
)
|
||||
async def rollback_version(
|
||||
place_id: UUID, req: Req_Rollback, service: SiteService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
return RemoveNoneResponse(await service.start_rollback(user_info, str(place_id), req))
|
||||
|
||||
|
||||
@router.get(path="/version/list", response_model=Res_SiteVersions, summary="빌드 버전 목록")
|
||||
async def list_versions(place_id: UUID, service: SiteService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
|
||||
return RemoveNoneResponse(await service.list_versions(user_info, str(place_id)))
|
||||
|
||||
@ -22,7 +22,7 @@ def is_configured() -> bool:
|
||||
|
||||
|
||||
def output_dir() -> Path:
|
||||
return Path(os.environ.get("SITE_OUTPUT_DIR", "/app/out/sites"))
|
||||
return Path(os.environ.get("SITE_OUTPUT_DIR", "/app/solution/site/out"))
|
||||
|
||||
|
||||
def _content_type(path: Path) -> str:
|
||||
@ -104,7 +104,7 @@ def _upload_shared(container, root: Path, prefix: str) -> set[str]:
|
||||
for entry in sorted(root.iterdir()):
|
||||
# `s/` 는 사이트별 디렉터리다 — 발행한 사이트 하나만 따로 올린다.
|
||||
# (여기서 함께 올리면 한 명이 발행할 때마다 전체 사이트를 다시 올리게 된다.)
|
||||
if entry.name.startswith(".") or entry.name == SITE_ROOT_DIR:
|
||||
if entry.name.startswith(".") or entry.name in {SITE_ROOT_DIR, "versions"}:
|
||||
continue
|
||||
if entry.is_dir():
|
||||
uploaded |= _upload_tree(container, root, entry.name, prefix)
|
||||
|
||||
@ -33,7 +33,7 @@ from services import (
|
||||
azure_static,
|
||||
indexnow,
|
||||
publish_gate,
|
||||
render_report,
|
||||
render_service,
|
||||
seo_keywords,
|
||||
site_payload,
|
||||
site_thumbnail,
|
||||
@ -46,9 +46,9 @@ from services.snapshot import build_snapshot
|
||||
_site_crud = SiteCRUD()
|
||||
_place_crud = PlaceCRUD()
|
||||
|
||||
# 렌더러가 이 버전을 굽고 보고서를 돌려줄 때까지 기다리는 시간.
|
||||
# ★ 넉넉해야 한다. 짧으면 멀쩡한 발행이 "렌더 대기 초과"로 실패한다 —
|
||||
# 렌더러는 payload 를 2초 주기로 보고, 사이트 하나 굽는 데 1초 남짓 걸린다.
|
||||
# 렌더러 subprocess 가 끝나기를 기다리는 시간(사진 내려받기 포함).
|
||||
# ★ 넉넉해야 한다. 짧으면 멀쩡한 발행이 "렌더 시간 초과"로 실패한다 — 처음 보는 사진을
|
||||
# 내려받는 발행은 몇 초가 더 걸린다(mirrorMedia, prerender.ts).
|
||||
RENDER_TIMEOUT_SEC = float(os.environ.get("RENDER_TIMEOUT_SEC") or 180)
|
||||
|
||||
|
||||
@ -73,10 +73,13 @@ async def ensure_site(place_id: str) -> "sites":
|
||||
return row
|
||||
|
||||
|
||||
async def _load_links(place_id: str) -> list:
|
||||
async def load_channel_links(place_id: str) -> list:
|
||||
"""채널 링크(야놀자·네이버 플레이스·인스타…).
|
||||
|
||||
스냅샷에 담기지 않는 유일한 발행 재료라 여기서 읽어 payload 로 넘긴다.
|
||||
스냅샷에 담기지 않는 유일한 발행 재료라 여기서 읽어 payload 로 넘긴다 — 재빌드(run_build)
|
||||
도 롤백(rollback_service.run_rollback)도 같은 함수를 쓴다. **스냅샷에는 안 싣는다** —
|
||||
롤백이 옛 스냅샷으로 다시 구워도 링크는 항상 지금 확정된 것을 보여줘야 한다(끊긴 링크를
|
||||
옛 버전째 되살리면 안 된다).
|
||||
★ 실패해도 빈 목록으로 진행한다 — 링크가 없다고 발행을 막을 이유가 없다."""
|
||||
err, rows = await DB_SESSION_MNG.execute_lambda(
|
||||
place_channels.DBType(),
|
||||
@ -249,24 +252,25 @@ async def run_build(job: dict) -> dict:
|
||||
site.status = SiteStatus.PUBLISHED.value
|
||||
site.published_at = site.published_at or now
|
||||
|
||||
links = await _load_links(place_id)
|
||||
payload_path = await emit_payload(place, snapshot, site, version, links)
|
||||
links = await load_channel_links(place_id)
|
||||
payload_path = await emit_payload(place, snapshot, site, version, links, publish=want_publish)
|
||||
if not payload_path:
|
||||
return await _fail("payload 를 쓰지 못했다 — 렌더러에 넘길 입력이 없다")
|
||||
result["payload_path"] = payload_path
|
||||
|
||||
slug = site_payload.publish_slug(place, site)
|
||||
|
||||
# ---- 렌더 결과를 기다린다 ----
|
||||
# ---- 렌더러를 직접 돌린다 ----
|
||||
# ★ 게이트는 **실제로 나갈 HTML** 을 보고 판정해야 한다. 렌더러가 자기 산출물을 대조해
|
||||
# 구조화 데이터 불일치와 고유 콘텐츠 수를 보고서로 돌려준다.
|
||||
report = await render_report.wait_for(slug, version_no, RENDER_TIMEOUT_SEC)
|
||||
if report is None:
|
||||
# ★ payload=False(미발행)면 렌더러는 `out/versions/<slug>/<version>/` 에만 굽고 공개
|
||||
# 주소(`out/s/<slug>`)는 건드리지 않는다 — emit_payload 에 실은 publish 플래그가 정한다.
|
||||
try:
|
||||
report = await render_service.render_site(payload_path, version_no, RENDER_TIMEOUT_SEC)
|
||||
except render_service.RenderFailed as ex:
|
||||
# ★ 발행하지 않는다. 페이지가 있는지 확인하지 못한 채 "발행됨"으로 남기면
|
||||
# 사장님은 [사이트 열기] 를 눌러야 404 로 알게 된다.
|
||||
return await _fail(
|
||||
f"렌더 결과를 {RENDER_TIMEOUT_SEC}초 안에 받지 못했다 — 렌더러가 도는지 확인이 필요하다"
|
||||
)
|
||||
return await _fail(str(ex))
|
||||
|
||||
mismatches = list(report.get("mismatches") or [])
|
||||
# ★ None(재지 못했다)과 0(재 봤더니 0건)을 뭉개지 않는다. 게이트는 raw 를 보고,
|
||||
@ -308,8 +312,9 @@ async def run_build(job: dict) -> dict:
|
||||
thumbnail_url = None
|
||||
if want_publish:
|
||||
try:
|
||||
await render_service.activate_site(slug, version_no)
|
||||
azure_result = await azure_static.publish(slug)
|
||||
except azure_static.AzurePublishError as ex:
|
||||
except (azure_static.AzurePublishError, render_service.RenderFailed) as ex:
|
||||
return await _fail(str(ex), None, stamp)
|
||||
if azure_result:
|
||||
result["azure"] = azure_result
|
||||
@ -383,4 +388,3 @@ async def run_build(job: dict) -> dict:
|
||||
version.build_status = BuildStatus.BUILT.value
|
||||
version.built_at = now
|
||||
return result
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ def is_configured() -> bool:
|
||||
|
||||
|
||||
def output_dir() -> Path:
|
||||
return Path(os.environ.get("SITE_OUTPUT_DIR", "/app/out/sites"))
|
||||
return Path(os.environ.get("SITE_OUTPUT_DIR", "/app/solution/site/out"))
|
||||
|
||||
|
||||
def site_urls(slug: str) -> list[str]:
|
||||
|
||||
@ -1,22 +1,18 @@
|
||||
"""렌더 보고서 — SSG 가 실제로 페이지를 구웠는지 확인하는 창구.
|
||||
|
||||
★ 왜 필요한가
|
||||
발행 잡은 payload JSON 을 쓰는 것까지가 끝이고, 방문자가 볼 HTML 은 프론트의 SSG 가 굽는다.
|
||||
그 사이가 끊겨 있었다. 프리렌더가 깨져도 DB 에는 "발행됨"으로 남고, 페이지는 없는데
|
||||
아무도 그걸 모른다 — 사장님이 [사이트 열기] 를 눌러야 404 로 발견된다.
|
||||
BUILD 잡은 payload JSON 을 쓰고 렌더러를 직접 돌린다(services/render_service). 렌더러는
|
||||
굽는 동안 있었던 일(성공/실패, 구조화 데이터 대조, 고유 콘텐츠 수)을 사이트마다
|
||||
`<payload_dir>/.status/<slug>.json` 에 남긴다 — 백엔드에 마운트된 디렉토리가 payload
|
||||
디렉토리뿐이라 보고서도 그 안에 둔다.
|
||||
|
||||
이제 프리렌더가 사이트마다 결과를 `<payload_dir>/.status/<slug>.json` 에 쓴다.
|
||||
백엔드에 마운트된 디렉토리가 payload 디렉토리뿐이라 보고서도 그 안에 둔다.
|
||||
|
||||
★ 여기서 하는 두 가지
|
||||
1. 렌더 상태 — 구워졌는가, 언제, 실패했으면 왜 (사이트 조회 API 가 그대로 내보낸다)
|
||||
2. 발행 대기 — BUILD 잡이 이 버전의 보고서를 기다렸다가, 그 결과로 발행 여부를 정한다
|
||||
(wait_for). 게이트는 **실제로 나갈 HTML** 을 보고 판정해야 한다.
|
||||
★ 여기서 하는 일 — 그 보고서를 읽는다.
|
||||
render_service 가 렌더러 subprocess 를 기다린 **직후** 한 번 읽어 발행 여부를 정하고,
|
||||
사이트 조회 API(render_status)는 아무 때나 다시 읽어 "지금 이 버전이 구워져 있나" 를 보여준다.
|
||||
|
||||
★ 읽기 전용이다. 보고서가 없거나 깨져 있어도 예외를 밖으로 내보내지 않는다 —
|
||||
사이트 조회가 부수 산출물 때문에 실패하면 안 된다.
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
@ -58,29 +54,6 @@ def read_report(slug: str) -> dict | None:
|
||||
return report
|
||||
|
||||
|
||||
async def wait_for(slug: str, site_version: int, timeout_sec: float, poll_sec: float = 1.0) -> dict | None:
|
||||
"""이 버전의 렌더 보고서가 나올 때까지 기다린다. 시간 안에 안 나오면 None.
|
||||
|
||||
★ 왜 기다리나
|
||||
발행 게이트는 **실제로 나갈 HTML** 을 보고 판정해야 한다. 그 HTML 은 프론트 렌더러가
|
||||
굽고, 렌더러는 payload 파일을 보고 도는 별도 프로세스다. 그래서 잡이 payload 를 쓴 뒤
|
||||
보고서가 올 때까지 기다렸다가, 그 결과로 발행 여부를 정한다.
|
||||
|
||||
★ 버전이 맞는 보고서만 받는다. 직전 버전 보고서를 보고 통과시키면 이번에 바뀐 값은
|
||||
아무도 검사하지 않은 채로 발행된다.
|
||||
|
||||
★ BUILD 잡은 heartbeat 로 lease 를 갱신하므로 여기서 기다려도 reaper 에 회수되지 않는다."""
|
||||
deadline = asyncio.get_running_loop().time() + timeout_sec
|
||||
while True:
|
||||
report = read_report(slug)
|
||||
if report is not None and report.get("siteVersion") == site_version:
|
||||
return report
|
||||
if asyncio.get_running_loop().time() >= deadline:
|
||||
LOG.w(f"[render] 보고서 대기 시간 초과 slug={slug} v{site_version} ({timeout_sec}초)")
|
||||
return None
|
||||
await asyncio.sleep(poll_sec)
|
||||
|
||||
|
||||
def render_status(slug: str, site_version: int | None = None) -> dict:
|
||||
"""사이트 조회 API 가 그대로 내보내는 렌더 상태.
|
||||
|
||||
|
||||
153
solution/backend/services/render_service.py
Normal file
153
solution/backend/services/render_service.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""렌더러 실행 — Python 워커가 미리 컴파일된 Node 렌더러를 subprocess 로 직접 돌린다.
|
||||
|
||||
★ 왜 (프리렌더를 워커 실행으로 통합, 2026-09-15)
|
||||
예전에는 `solution-prerender` 라는 별도 상시 컨테이너가 payloads/ 디렉토리를 2초마다
|
||||
폴링하며 바뀐 파일을 구웠고, BUILD 잡은 `.status/<slug>.json` 이 나타나기를 또 폴링했다
|
||||
(render_report.wait_for). 컨테이너 하나·폴링 두 겹이 전부 "굽는 걸 어떻게 아느냐"는
|
||||
같은 문제를 풀고 있었다 — 워커가 굽기를 직접 돌리면 둘 다 필요 없다: subprocess 가
|
||||
끝나는 순간이 곧 "구워졌다"는 신호다.
|
||||
|
||||
★ 이 파일은 payload → 렌더러 실행 → 구조화된 결과, 그 경계만 다룬다.
|
||||
Python 은 여전히 HTML 을 만들지 않는다(ARCHITECTURE 1절) — 여기서 하는 일은 미리 빌드된
|
||||
JS 파일 하나를 `node` 로 실행하고 그 결과 보고서(.status/<slug>.json, prerender.ts 가 쓴다)
|
||||
를 읽어 돌려주는 것뿐이다. 렌더러 코드 자체는 solution/site 소유 그대로다.
|
||||
|
||||
★ 워커 이미지에는 컴파일된 렌더러(`dist/client` · `dist/prerender/prerender.js`)와 `public/`
|
||||
만 있으면 된다 — node_modules 는 필요 없다(vite.config.ts `ssr.noExternal: true`, 실측:
|
||||
node_modules 를 지우고 실행해도 그대로 돈다). 기동·잡 실행 중에 npm install 을 하지 않는다.
|
||||
"""
|
||||
import asyncio
|
||||
import fcntl
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from common.logger import LOG
|
||||
from services import render_report
|
||||
|
||||
# 컴파일된 렌더러 진입점. Dockerfile 의 site-builder 스테이지가 여기에 굽는다.
|
||||
RENDERER_ENTRY_ENV = "RENDERER_ENTRY"
|
||||
DEFAULT_RENDERER_ENTRY = "/app/solution/site/dist/prerender/prerender.js"
|
||||
|
||||
NODE_BIN_ENV = "NODE_BIN"
|
||||
DEFAULT_NODE_BIN = "node"
|
||||
|
||||
# 렌더러가 굽는 산출물 루트(out/). services/azure_static·indexnow 와 **같은 env** 를 본다 —
|
||||
# 렌더러가 쓴 자리와 백엔드가 훑는 자리가 어긋나면 "구웠는데 없다"는 조용한 실패가 난다.
|
||||
OUTPUT_DIR_ENV = "SITE_OUTPUT_DIR"
|
||||
DEFAULT_OUTPUT_DIR = "/app/solution/site/out"
|
||||
|
||||
|
||||
class RenderFailed(RuntimeError):
|
||||
"""렌더러 프로세스 자체가 죽었거나(비정상 종료) 보고서를 남기지 못했다.
|
||||
|
||||
★ 게이트 반려(구조화 데이터 불일치·고유 콘텐츠 0건)는 여기 안 걸린다 — 그건 exit code
|
||||
1 이어도 보고서가 정상적으로 남으므로 호출측이 report.ok 로 판단한다. 이 예외는
|
||||
"무슨 일이 있었는지조차 모른다" 는 경우만 위한 것이다."""
|
||||
|
||||
|
||||
def renderer_entry() -> str:
|
||||
return os.environ.get(RENDERER_ENTRY_ENV) or DEFAULT_RENDERER_ENTRY
|
||||
|
||||
|
||||
def output_dir() -> str:
|
||||
return os.environ.get(OUTPUT_DIR_ENV) or DEFAULT_OUTPUT_DIR
|
||||
|
||||
|
||||
async def _run_subprocess(args: list[str], timeout_sec: float) -> tuple[int | None, str]:
|
||||
"""같은 볼륨의 자산 대장·사이트맵은 한 프로세스만 갱신한다."""
|
||||
root = Path(output_dir())
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
with (root / ".render.lock").open("a") as lock:
|
||||
try:
|
||||
async with asyncio.timeout(timeout_sec):
|
||||
while True:
|
||||
try:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
break
|
||||
except BlockingIOError:
|
||||
await asyncio.sleep(0.1)
|
||||
return await _run_unlocked(args, timeout_sec)
|
||||
except TimeoutError:
|
||||
return None, "렌더 대기 또는 실행 시간 초과"
|
||||
finally:
|
||||
fcntl.flock(lock, fcntl.LOCK_UN)
|
||||
|
||||
|
||||
async def _run_unlocked(args: list[str], timeout_sec: float) -> tuple[int | None, str]:
|
||||
"""렌더러 프로세스를 돌리고 (exit_code, stderr 일부) 를 돌려준다.
|
||||
|
||||
★ 타임아웃이면 프로세스를 죽이고 회수한다 — 좀비로 남겨 워커 컨테이너의 프로세스 표를
|
||||
채우면 안 된다. exit_code 는 None 으로 남아 "시간 안에 안 끝났다" 를 구분한다."""
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*args,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
_stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec)
|
||||
except (asyncio.TimeoutError, TimeoutError):
|
||||
proc.kill()
|
||||
# kill 후에도 반드시 wait 한다 — 안 하면 좀비 프로세스로 남는다.
|
||||
await proc.wait()
|
||||
return None, f"{timeout_sec:.0f}초 안에 끝나지 않아 강제 종료했다"
|
||||
except asyncio.CancelledError:
|
||||
if proc.returncode is None:
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
raise
|
||||
# 사유는 앞부분에 나온다(watch-payloads.mjs 의 run() 과 같은 규칙) — 스택 전체를 들 이유가 없다.
|
||||
return proc.returncode, stderr.decode("utf-8", errors="replace")[:4000]
|
||||
|
||||
|
||||
async def render_site(payload_path: str, site_version: int, timeout_sec: float) -> dict:
|
||||
"""payload 파일 하나를 굽는다. 끝나면 렌더 보고서(dict)를 돌려준다.
|
||||
|
||||
★ 게이트 판정은 여기서 하지 않는다 — 반환된 보고서의 `ok`·`mismatches`·
|
||||
`uniqueContentCount` 를 build_service.run_build 가 그대로 판단한다(예전과 같은 계약,
|
||||
render_report.wait_for 가 폴링해서 얻던 것과 같은 모양이다).
|
||||
★ 멱등이다 — 같은 payload 파일로 다시 부르면 같은 결과가 나온다(재시도 안전). 렌더러
|
||||
자신이 사이트별로 실패를 격리하므로, 이미 성공한 사이트를 다시 구워도 결과는 같다."""
|
||||
slug = Path(payload_path).stem
|
||||
# 재시도에서 같은 버전의 옛 성공 보고서를 이번 실행 결과로 읽지 않는다.
|
||||
render_report.report_path(slug).unlink(missing_ok=True)
|
||||
args = [
|
||||
os.environ.get(NODE_BIN_ENV) or DEFAULT_NODE_BIN,
|
||||
renderer_entry(),
|
||||
f"--payload={payload_path}",
|
||||
f"--out={output_dir()}",
|
||||
"--stage-only",
|
||||
]
|
||||
try:
|
||||
code, stderr = await _run_subprocess(args, timeout_sec)
|
||||
except OSError as ex:
|
||||
raise RenderFailed(f"렌더러 실행 실패: {type(ex).__name__}") from ex
|
||||
|
||||
report = render_report.read_report(slug)
|
||||
if code in (0, 1) and report is not None and report.get("siteVersion") == site_version:
|
||||
return report
|
||||
|
||||
# ★ 프로세스가 죽거나 시간 초과였는데 보고서도 없으면(사이트를 하나도 돌기 전에 죽은 경우)
|
||||
# 대신 실패로 남긴다 — 안 그러면 build_service 가 "렌더 결과를 못 받았다" 는 것만 알고
|
||||
# 진짜 사유(모듈 누락·OOM 등)는 워커 로그에만 남는다.
|
||||
reason = (
|
||||
f"렌더러가 {timeout_sec:.0f}초 안에 끝나지 않았다"
|
||||
if code is None
|
||||
else f"렌더러가 비정상 종료했다(exit {code})" if code != 0 else "보고서를 남기지 못했다(버전 불일치 포함)"
|
||||
)
|
||||
detail = f"{reason}" + (f" — {stderr.strip()}" if stderr.strip() else "")
|
||||
LOG.w(f"[render] {slug} v{site_version}: {detail}")
|
||||
raise RenderFailed(detail)
|
||||
|
||||
|
||||
async def activate_site(slug: str, site_version: int) -> None:
|
||||
"""두 게이트를 통과한 뒤 Node가 공개 링크만 전환한다."""
|
||||
args = [
|
||||
os.environ.get(NODE_BIN_ENV) or DEFAULT_NODE_BIN,
|
||||
renderer_entry(), f"--out={output_dir()}", f"--activate={slug}:{site_version}",
|
||||
]
|
||||
try:
|
||||
code, _stderr = await _run_subprocess(args, 30)
|
||||
except OSError as ex:
|
||||
raise RenderFailed(f"공개 전환 실행 실패: {type(ex).__name__}") from ex
|
||||
if code != 0:
|
||||
raise RenderFailed("공개 버전 전환 실패")
|
||||
158
solution/backend/services/rollback_service.py
Normal file
158
solution/backend/services/rollback_service.py
Normal file
@ -0,0 +1,158 @@
|
||||
"""예전 버전으로 되돌리기 — ROLLBACK 잡이 하는 일.
|
||||
|
||||
대상 버전 스냅샷 로드 → payload 재조립 → 렌더 → 게이트 → 공개 주소 전환
|
||||
|
||||
★ 재수집·재생성을 하지 않는다. run_build 와 다른 점이 이것 하나다 — 대상 버전이
|
||||
박제해 둔 snapshot(site_versions.snapshot)을 **그대로** 다시 payload 로 옮길 뿐이다.
|
||||
사장님이 그때 이미 확인하고 발행했던 값이므로, 지금 fact 상태가 바뀌었어도 롤백은
|
||||
그 시점의 값을 그대로 복원한다 — "롤백했는데 최신 수정이 섞여 있다"를 막는다.
|
||||
|
||||
★ 그래도 게이트는 다시 돈다. 스냅샷은 그대로지만 **렌더러는 그때와 다를 수 있다**
|
||||
(그 사이 배포됐을 수 있다) — 새 렌더러가 옛 스냅샷을 어떻게 굽는지는 다시 확인해야 한다.
|
||||
|
||||
★ 재굽기가 필요 없을 수도 있다 — 대상 버전 디렉토리가 보관 기간 안에 아직 디스크에 있으면
|
||||
렌더러가 그 사실을 알아서 이용한다(prerender.ts publishVersion 은 렌더 자체는 매번 다시
|
||||
하지만, 결과가 같으면 파일 내용도 같다 — 멱등이다). 디스크에서 지워졌어도 문제없다 —
|
||||
snapshot 이 있으니 처음부터 다시 굽는다.
|
||||
|
||||
★ 실패하면 지금 공개된 버전은 그대로다. render_service.render_site 가 실패를 던지거나
|
||||
report.ok=False 면 심볼릭 링크를 아예 안 돌린다(prerender.ts publishVersion 은 성공한
|
||||
렌더 결과에만 닿는다) — 발행 파이프라인과 같은 원자성 보장이다.
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from common.database.db_session_manager import DB_SESSION_MNG
|
||||
from common.database.model.models import places, site_publish_logs, site_versions, sites
|
||||
from common.enums import (
|
||||
BuildStatus,
|
||||
DBWRType,
|
||||
ErrorType,
|
||||
PlaceCategory,
|
||||
PublishAction,
|
||||
PublishResult,
|
||||
SiteStatus,
|
||||
)
|
||||
from common.logger import LOG
|
||||
from common.utils.gtime import GTime
|
||||
from crud.place_crud import PlaceCRUD
|
||||
from crud.site_crud import SiteCRUD
|
||||
from services import azure_static, indexnow, publish_gate, render_service, site_payload
|
||||
from services.build_service import ensure_site, load_channel_links
|
||||
|
||||
_site_crud = SiteCRUD()
|
||||
_place_crud = PlaceCRUD()
|
||||
|
||||
RENDER_TIMEOUT_SEC_ENV = "RENDER_TIMEOUT_SEC"
|
||||
|
||||
|
||||
class RollbackAborted(RuntimeError):
|
||||
"""재시도해도 소용없는 중단 — 잡의 last_error 로 남는다."""
|
||||
|
||||
|
||||
async def _log(site_id, version_id, result: PublishResult, gate=None, actor=None):
|
||||
row = site_publish_logs(
|
||||
site_id=site_id,
|
||||
site_version_id=version_id,
|
||||
action=PublishAction.ROLLBACK.value,
|
||||
result=result.value,
|
||||
reject_reason=(gate.reason.value if gate is not None and gate.reason else None),
|
||||
detail=(gate.as_log() if gate is not None and not gate.passed else None),
|
||||
actor_user_id=uuid.UUID(actor) if actor else None,
|
||||
)
|
||||
await DB_SESSION_MNG.execute_lambda_run([site_publish_logs.DBType()], [lambda s: _site_crud.add_log(s, row)])
|
||||
|
||||
|
||||
async def run_rollback(job: dict) -> dict:
|
||||
"""ROLLBACK 잡 핸들러. payload: {place_id, owner_user_id, target_version, requested_by?}"""
|
||||
payload = job["payload"]
|
||||
place_id = payload["place_id"]
|
||||
owner_user_id = payload["owner_user_id"]
|
||||
target_version = int(payload["target_version"])
|
||||
render_timeout_sec = float(os.environ.get(RENDER_TIMEOUT_SEC_ENV) or 180)
|
||||
|
||||
err, place = await DB_SESSION_MNG.execute_lambda(
|
||||
places.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: _place_crud.get_place(s, uuid.UUID(owner_user_id), uuid.UUID(place_id)),
|
||||
)
|
||||
if err != ErrorType.SUCCESS or place is None:
|
||||
raise RollbackAborted(f"사업장을 찾을 수 없다: {place_id}")
|
||||
|
||||
site = await ensure_site(place_id)
|
||||
|
||||
v_err, version = await DB_SESSION_MNG.execute_lambda(
|
||||
site_versions.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: _site_crud.get_version_by_number(s, site.site_id, target_version),
|
||||
)
|
||||
if v_err != ErrorType.SUCCESS or version is None:
|
||||
raise RollbackAborted(f"버전 {target_version} 을 찾을 수 없다")
|
||||
if version.build_status != BuildStatus.BUILT.value or not version.snapshot:
|
||||
raise RollbackAborted(f"버전 {target_version} 은 성공적으로 빌드된 적이 없어 되돌릴 수 없다")
|
||||
if site.current_version_id == version.site_version_id:
|
||||
raise RollbackAborted(f"이미 지금 공개 중인 버전이다(v{target_version})")
|
||||
|
||||
result = {
|
||||
"place_id": place_id, "site_id": str(site.site_id),
|
||||
"target_version": target_version, "site_version_id": str(version.site_version_id),
|
||||
}
|
||||
|
||||
async def _fail(reason: str, gate: publish_gate.GateResult | None = None):
|
||||
await _log(site.site_id, version.site_version_id, PublishResult.REJECTED if gate else PublishResult.FAILED,
|
||||
gate, payload.get("requested_by"))
|
||||
result["rolled_back"] = False
|
||||
result["error"] = reason
|
||||
LOG.w(f"[rollback] place={place_id} v{target_version} 실패: {reason}")
|
||||
return result
|
||||
|
||||
# ★ snapshot 을 그대로 옮긴다 — 재수집하지 않는다(파일 머리주석 참조).
|
||||
links = await load_channel_links(place_id)
|
||||
payload_dict = await site_payload.prepare_site_payload(
|
||||
place, version.snapshot, site, version, links, publish=True
|
||||
)
|
||||
payload_path = site_payload.write_payload(payload_dict)
|
||||
result["payload_path"] = payload_path
|
||||
|
||||
try:
|
||||
report = await render_service.render_site(payload_path, target_version, render_timeout_sec)
|
||||
except render_service.RenderFailed as ex:
|
||||
return await _fail(str(ex))
|
||||
|
||||
mismatches = list(report.get("mismatches") or [])
|
||||
unique_count_raw = report.get("uniqueContentCount")
|
||||
|
||||
# ★ 렌더러가 그때와 달라졌을 수 있으므로 게이트를 다시 돈다(파일 머리주석 참조) —
|
||||
# fact 검증 게이트(1차)는 건너뛴다. snapshot 은 이미 확인된 값만 담아 발행했던 것이라
|
||||
# 다시 물을 것이 없다 — 여기서 또 물으면 "그때는 됐는데 지금은 안 된다"는 모순이 난다.
|
||||
gate = publish_gate.evaluate(
|
||||
PlaceCategory(place.category), version.snapshot.get("facts") or [], unique_count_raw, mismatches
|
||||
)
|
||||
result["gate"] = {"passed": gate.passed, **gate.as_log()}
|
||||
if not gate.passed:
|
||||
return await _fail(f"{gate.reason.name}: {gate.as_log()}", gate)
|
||||
if not report.get("ok"):
|
||||
return await _fail(str(report.get("error") or "렌더 실패"))
|
||||
|
||||
slug = site_payload.publish_slug(place, site)
|
||||
try:
|
||||
await render_service.activate_site(slug, target_version)
|
||||
await azure_static.publish(slug)
|
||||
except (render_service.RenderFailed, azure_static.AzurePublishError) as ex:
|
||||
return await _fail(str(ex))
|
||||
await indexnow.submit(slug)
|
||||
|
||||
now = GTime.UTC()
|
||||
site_update = {
|
||||
"status": SiteStatus.PUBLISHED.value,
|
||||
"current_version_id": version.site_version_id,
|
||||
"published_at": now,
|
||||
}
|
||||
await DB_SESSION_MNG.execute_lambda_claim(
|
||||
sites.DBType(), lambda s: _site_crud.update_site(s, site.site_id, site_update)
|
||||
)
|
||||
await _log(site.site_id, version.site_version_id, PublishResult.SUCCESS, None, payload.get("requested_by"))
|
||||
|
||||
result["rolled_back"] = True
|
||||
LOG.i(f"[rollback] place={place_id} v{target_version} 로 되돌림")
|
||||
return result
|
||||
@ -39,8 +39,13 @@ from services.stay_guide import nol_stay_guide
|
||||
SCHEMA_VERSION = 1
|
||||
|
||||
# 출력 디렉토리. 컨테이너 밖(볼륨·오브젝트 스토리지)으로 빼기 쉬우라고 env 로 둔다.
|
||||
# ★ 기본값이 `solution/site/payloads` 아래인 이유 — 렌더러(prerender.ts)가 songs·out 디렉토리를
|
||||
# **자기 파일 위치 기준 상대경로**로 찾는다(SITE_ROOT = dist/prerender/../..). 워커가 그
|
||||
# 렌더러를 subprocess 로 직접 띄우면서(render_service.py) 세 디렉토리(payloads·songs·out)가
|
||||
# 그 렌더러가 실제로 설치된 자리(`/app/solution/site/`) 아래에 나란히 있어야 한다 —
|
||||
# 어긋나면 워커는 payload 를 잘 쓰는데 렌더러는 다른 곳에서 songs 를 찾다가 못 찾는다.
|
||||
PAYLOAD_DIR_ENV = "SITE_PAYLOAD_DIR"
|
||||
DEFAULT_PAYLOAD_DIR = "/app/out/payloads"
|
||||
DEFAULT_PAYLOAD_DIR = "/app/solution/site/payloads"
|
||||
|
||||
# 커스텀 도메인이 없을 때 쓰는 기본 호스트. sites.domain 이 채워지면 그 값이 이긴다.
|
||||
#
|
||||
@ -780,11 +785,15 @@ def region_label(*addresses: str | None) -> str | None:
|
||||
|
||||
|
||||
# ── payload 조립 ──────────────────────────────────────────────────────────
|
||||
def to_site_payload(place, snapshot: dict, site, version, links) -> dict:
|
||||
def to_site_payload(place, snapshot: dict, site, version, links, publish: bool = False) -> dict:
|
||||
"""스냅샷 + 사이트/버전 행 + 채널 링크 → SitePayload(dict).
|
||||
|
||||
순수 변환 함수다. DB 도 파일도 건드리지 않는다 — 그래야 테스트가 쉽고,
|
||||
같은 입력이면 언제나 같은 payload 가 나온다."""
|
||||
같은 입력이면 언제나 같은 payload 가 나온다.
|
||||
|
||||
★ `publish` 는 렌더러(prerender.ts publishVersion)에게 "이 버전으로 공개 주소를
|
||||
넘겨도 되는가"를 알리는 신호다. False(미리보기·게이트 통과 전 재빌드)면 렌더러가
|
||||
`out/versions/<slug>/<version>/` 에만 굽고 `out/s/<slug>` 심볼릭 링크는 그대로 둔다."""
|
||||
snapshot = snapshot or {}
|
||||
snap_place = snapshot.get("place") or {}
|
||||
place_id = str(_get(place, "place_id"))
|
||||
@ -929,6 +938,7 @@ def to_site_payload(place, snapshot: dict, site, version, links) -> dict:
|
||||
"publishedAt": published_at or updated_at,
|
||||
"updatedAt": updated_at,
|
||||
"version": int(_get(version, "version") or 1),
|
||||
"publish": bool(publish),
|
||||
},
|
||||
"place": {
|
||||
"name": name,
|
||||
@ -1037,9 +1047,9 @@ def write_payload(payload: dict) -> str:
|
||||
return str(path)
|
||||
|
||||
|
||||
async def prepare_site_payload(place, snapshot: dict, site, version, links) -> dict:
|
||||
async def prepare_site_payload(place, snapshot: dict, site, version, links, publish: bool = False) -> dict:
|
||||
"""미리보기·발행 공통 보강. 요약은 DB 스냅샷이 아니라 응답/산출물에만 싣는다."""
|
||||
payload = to_site_payload(place, snapshot, site, version, links)
|
||||
payload = to_site_payload(place, snapshot, site, version, links, publish)
|
||||
for fact in payload["facts"]:
|
||||
if fact["key"] != "intro" or fact["status"] not in {s.value for s in PUBLISHABLE_FACT_STATUSES}:
|
||||
continue
|
||||
@ -1054,14 +1064,14 @@ async def prepare_site_payload(place, snapshot: dict, site, version, links) -> d
|
||||
return payload
|
||||
|
||||
|
||||
async def emit_payload(place, snapshot: dict, site, version, links) -> str | None:
|
||||
async def emit_payload(place, snapshot: dict, site, version, links, publish: bool = False) -> str | None:
|
||||
"""payload 조립 + 파일 쓰기. 실패해도 예외를 밖으로 내보내지 않는다.
|
||||
|
||||
★ 발행 자체를 실패시키면 안 된다 — 게이트를 통과해 DB 에 남은 발행 기록은 이미 정확하고,
|
||||
payload 는 그것을 화면으로 옮기는 부수 산출물이다. 디스크가 없거나 권한이 없어서
|
||||
발행이 되돌려지는 게 더 나쁘다. 대신 경고 로그로 반드시 드러낸다."""
|
||||
try:
|
||||
payload = await prepare_site_payload(place, snapshot, site, version, links)
|
||||
payload = await prepare_site_payload(place, snapshot, site, version, links, publish)
|
||||
path = write_payload(payload)
|
||||
LOG.i(
|
||||
f"[payload] {payload['site']['slug']} → {path} "
|
||||
|
||||
@ -29,6 +29,7 @@ from router.v1.site.protocol import (
|
||||
Req_SiteStatus,
|
||||
Req_SiteTemplate,
|
||||
Req_SiteTheme,
|
||||
Req_Rollback,
|
||||
Req_StartBuild,
|
||||
Res_MySites,
|
||||
Res_PublishLogs,
|
||||
@ -166,7 +167,7 @@ class SiteService:
|
||||
(`snapshot.build_snapshot` → `site_payload.prepare_site_payload`)를 그대로 거치므로,
|
||||
여기서 갈릴 자리가 없다. 버전은 아직 없으니 0 으로 넘긴다 — 화면에 안 쓰인다.
|
||||
"""
|
||||
from services.build_service import ensure_site, _load_links
|
||||
from services.build_service import ensure_site, load_channel_links
|
||||
from services.site_payload import prepare_site_payload
|
||||
from services.snapshot import build_snapshot
|
||||
|
||||
@ -175,7 +176,7 @@ class SiteService:
|
||||
return None
|
||||
|
||||
site = await ensure_site(place_id)
|
||||
links = await _load_links(place_id)
|
||||
links = await load_channel_links(place_id)
|
||||
snapshot = await build_snapshot(place)
|
||||
# ★ version 은 None 이다. 발행 전이라 버전 행이 없고, payload 의 site.version 은
|
||||
# 캐시 무효화 키라 미리보기에서는 뜻이 없다(to_site_payload 가 0 으로 떨어뜨린다).
|
||||
@ -570,6 +571,35 @@ class SiteService:
|
||||
res.created = created
|
||||
return res
|
||||
|
||||
async def start_rollback(self, user_info: UserInfo, place_id: str, req: Req_Rollback) -> Res_StartBuild:
|
||||
"""예전 버전으로 공개 주소를 되돌리는 잡을 큐에 넣는다.
|
||||
|
||||
★ dedupe_key 를 빌드와 **같은 것**(`build:{place_id}`)을 쓴다 — 롤백 도중에 새 발행이
|
||||
끼어들거나 그 반대가 되면 어느 쪽이 이겼는지 알 수 없는 상태가 된다. 이 사이트의
|
||||
공개 주소를 바꾸는 작업은 항상 하나만 활성화된다."""
|
||||
res = Res_StartBuild()
|
||||
err_type, _place = await self._load_place(user_info, place_id)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
return res
|
||||
|
||||
job_id, created = await enqueue_job(
|
||||
self.queue, JobType.ROLLBACK,
|
||||
{
|
||||
"place_id": place_id, "owner_user_id": user_info.user_id,
|
||||
"target_version": req.target_version, "requested_by": user_info.user_id,
|
||||
},
|
||||
dedupe_key=f"build:{place_id}",
|
||||
)
|
||||
if job_id is None:
|
||||
res.result.SetResult(ErrorType.COLLECT_ALREADY_RUNNING)
|
||||
return res
|
||||
|
||||
res.job_id = uuid.UUID(job_id)
|
||||
res.status = JobStatus.PENDING
|
||||
res.created = created
|
||||
return res
|
||||
|
||||
async def list_versions(self, user_info: UserInfo, place_id: str) -> Res_SiteVersions:
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ async def _generate_kind(client: httpx.AsyncClient, kind: str, region_label: str
|
||||
{"role": "system", "content": prompts.SYSTEM_PROMPT},
|
||||
{"role": "user", "content": prompts.build_prompt(kind, region_label)},
|
||||
],
|
||||
"max_tokens": perplexity.DEFAULT_MAX_TOKENS,
|
||||
"max_tokens": _MAX_TOKENS.get(kind, perplexity.DEFAULT_MAX_TOKENS),
|
||||
}
|
||||
try:
|
||||
payload = await perplexity.call(body, client=client)
|
||||
@ -80,6 +80,15 @@ async def _generate_kind(client: httpx.AsyncClient, kind: str, region_label: str
|
||||
return items, dropped
|
||||
|
||||
|
||||
# 종류별 응답 토큰 상한. 기본(2048)으로 모자란 종류만 적는다.
|
||||
#
|
||||
# ★ 왜 (실측 2026-09-15) `reading` 은 30~34꼭지 × 서너 문장이라 다른 종류의 서너 배다.
|
||||
# 2048 로 부르면 응답이 **문장 한가운데서 잘려** 오고, 파서는 그걸 "JSON 이 아니다" 로
|
||||
# 통째로 버린다 — 여섯 지역 전부 0건이었다. 잘린 것은 재시도해도 같은 자리에서 잘린다.
|
||||
# ★ 상한만 올린다. 꼭지 수를 줄이면 화면이 매번 5~6개만 뽑는 의미가 없어진다
|
||||
# (`site/sections/items/ReadingSection.tsx`).
|
||||
_MAX_TOKENS = {"reading": 8000}
|
||||
|
||||
# 사진을 무엇으로 찾을지. 종류마다 출처가 다르다.
|
||||
#
|
||||
# chronicle · postcard 공공데이터(TourAPI) — 장소·시설 사진. 공공누리 Type1/Type3 만
|
||||
|
||||
@ -100,10 +100,10 @@ async def test_unverified_fact_blocks_publish(auth_headers, client, db_engine):
|
||||
(필수 항목 누락은 2026-08-27 부터 막지 않는다. 그래서 여기서 걸리는 사유가 바뀌었다.)"""
|
||||
h = await auth_headers("u1")
|
||||
pid = await _place(client, h, "미검증펜션")
|
||||
# 크롤링으로만 들어온 값 = 후보 상태
|
||||
# 크롤링은 바로 노출되는 정책이다. 실제 미검증 상태를 만드는 API 출처로 검사한다.
|
||||
for k, v in REQUIRED.items():
|
||||
await client.post(f"/v1/place/{pid}/fact", headers=h, json={
|
||||
"key": k, "value": v, "source_type": SourceType.CRAWL.value, "source_url": "https://ota.test/1"})
|
||||
"key": k, "value": v, "source_type": SourceType.API.value, "source_url": "https://api.test/1"})
|
||||
|
||||
job_id = (await client.post(f"/v1/place/{pid}/site/build", headers=h, json={"publish": True})).json()["job_id"]
|
||||
await _run()
|
||||
@ -292,17 +292,17 @@ async def _build(client, h, pid, publish=True):
|
||||
async def test_렌더가_실패하면_발행하지_않는다(auth_headers, client, db_engine, monkeypatch):
|
||||
"""검증: 렌더러가 페이지를 굽지 못했다고 보고한다.
|
||||
기대결과: ★ FAILED. 페이지가 없는데 "발행됨"으로 남으면 사장님은 404 를 눌러야 안다."""
|
||||
from services import render_report
|
||||
from services import render_service
|
||||
|
||||
async def _broken(slug, site_version, timeout_sec, poll_sec=1.0):
|
||||
return {"schemaVersion": 1, "slug": slug, "siteId": "", "placeId": "",
|
||||
async def _broken(payload_path, site_version, timeout_sec):
|
||||
return {"schemaVersion": 1, "slug": "t", "siteId": "", "placeId": "",
|
||||
"siteVersion": site_version, "ok": False, "renderedAt": "2026-01-01T00:00:00Z",
|
||||
"routes": 0, "bundle": "t.js", "uniqueContentCount": 5, "jsonld": [],
|
||||
"mismatches": [], "error": "번들이 없다"}
|
||||
|
||||
h = await auth_headers("u1")
|
||||
pid = await _built_place(client, h, db_engine)
|
||||
monkeypatch.setattr(render_report, "wait_for", _broken)
|
||||
monkeypatch.setattr(render_service, "render_site", _broken)
|
||||
|
||||
r = await _build(client, h, pid)
|
||||
assert r["build_status"] == "FAILED"
|
||||
@ -316,10 +316,10 @@ async def test_렌더가_실패하면_발행하지_않는다(auth_headers, clien
|
||||
async def test_구조화데이터가_화면과_다르면_발행하지_않는다(auth_headers, client, db_engine, monkeypatch):
|
||||
"""검증: 렌더러가 JSON-LD 불일치를 보고한다.
|
||||
기대결과: ★ JSONLD_MISMATCH — 검색엔진에만 다른 말을 하는 페이지는 나가면 안 된다."""
|
||||
from services import render_report
|
||||
from services import render_service
|
||||
|
||||
async def _mismatch(slug, site_version, timeout_sec, poll_sec=1.0):
|
||||
return {"schemaVersion": 1, "slug": slug, "siteId": "", "placeId": "",
|
||||
async def _mismatch(payload_path, site_version, timeout_sec):
|
||||
return {"schemaVersion": 1, "slug": "t", "siteId": "", "placeId": "",
|
||||
"siteVersion": site_version, "ok": False, "renderedAt": "2026-01-01T00:00:00Z",
|
||||
"routes": 0, "bundle": "t.js", "uniqueContentCount": 5, "jsonld": [],
|
||||
"mismatches": ["telephone: '02-0000-0000' 이 화면에 없다"],
|
||||
@ -327,7 +327,7 @@ async def test_구조화데이터가_화면과_다르면_발행하지_않는다(
|
||||
|
||||
h = await auth_headers("u1")
|
||||
pid = await _built_place(client, h, db_engine)
|
||||
monkeypatch.setattr(render_report, "wait_for", _mismatch)
|
||||
monkeypatch.setattr(render_service, "render_site", _mismatch)
|
||||
|
||||
r = await _build(client, h, pid)
|
||||
assert r["build_status"] == "FAILED"
|
||||
@ -339,14 +339,14 @@ async def test_구조화데이터가_화면과_다르면_발행하지_않는다(
|
||||
async def test_렌더_결과가_안_오면_발행하지_않는다(auth_headers, client, db_engine, monkeypatch):
|
||||
"""검증: 렌더러가 죽어 보고서가 오지 않는다(타임아웃).
|
||||
기대결과: ★ FAILED. 확인하지 못한 것을 발행됨으로 남기지 않는다."""
|
||||
from services import render_report
|
||||
from services import render_service
|
||||
|
||||
async def _timeout(slug, site_version, timeout_sec, poll_sec=1.0):
|
||||
return None
|
||||
async def _timeout(payload_path, site_version, timeout_sec):
|
||||
raise render_service.RenderFailed("렌더 결과를 받지 못했다 — 렌더러가 도는지 확인이 필요하다")
|
||||
|
||||
h = await auth_headers("u1")
|
||||
pid = await _built_place(client, h, db_engine)
|
||||
monkeypatch.setattr(render_report, "wait_for", _timeout)
|
||||
monkeypatch.setattr(render_service, "render_site", _timeout)
|
||||
|
||||
r = await _build(client, h, pid)
|
||||
assert r["build_status"] == "FAILED"
|
||||
|
||||
38
solution/backend/tests/test_render_process.py
Normal file
38
solution/backend/tests/test_render_process.py
Normal file
@ -0,0 +1,38 @@
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from services import render_service
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fake_renderer(monkeypatch, tmp_path):
|
||||
# 이 파일은 conftest의 렌더 대역 대신 실제 자식 프로세스 수명만 검사한다.
|
||||
monkeypatch.setenv("SITE_OUTPUT_DIR", str(tmp_path))
|
||||
|
||||
|
||||
async def test_process_success():
|
||||
code, _ = await render_service._run_subprocess([sys.executable, "-c", "print(1)"], 2)
|
||||
assert code == 0
|
||||
|
||||
|
||||
async def test_timeout_releases_lock():
|
||||
code, _ = await render_service._run_subprocess(
|
||||
[sys.executable, "-c", "import time;time.sleep(5)"], 0.1
|
||||
)
|
||||
assert code is None
|
||||
code, _ = await render_service._run_subprocess([sys.executable, "-c", "print(1)"], 2)
|
||||
assert code == 0
|
||||
|
||||
|
||||
async def test_cancel_releases_lock():
|
||||
task = asyncio.create_task(render_service._run_subprocess(
|
||||
[sys.executable, "-c", "import time;time.sleep(5)"], 10
|
||||
))
|
||||
await asyncio.sleep(0.1)
|
||||
task.cancel()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
code, _ = await render_service._run_subprocess([sys.executable, "-c", "print(1)"], 2)
|
||||
assert code == 0
|
||||
131
solution/backend/tests/test_rollback.py
Normal file
131
solution/backend/tests/test_rollback.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""롤백 — 예전 버전으로 공개 주소를 되돌린다.
|
||||
|
||||
★ 이 경로가 절대 하면 안 되는 것:
|
||||
- 재수집·재생성으로 스냅샷을 새로 만드는 것 (그 버전이 발행됐던 그대로여야 한다)
|
||||
- 실패했는데 site.current_version_id 를 바꾸는 것 (직전 공개본이 그대로 있어야 한다)
|
||||
- 지금 공개 중인 버전으로 "롤백"을 허용하는 것 (의미 없는 잡을 만든다)
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from common.enums import BuildStatus, JobStatus, SiteStatus
|
||||
from crud.job_crud import JobQueue
|
||||
from worker.handlers import build_handler
|
||||
from worker.runner import Worker
|
||||
|
||||
from tests.test_build_publish import _approved_media, _place, _verified_facts
|
||||
|
||||
|
||||
async def _run():
|
||||
assert await Worker("w", JobQueue(), build_handler(), job_deadline_sec=60).process_one() is True
|
||||
|
||||
|
||||
async def _publish(client, h, pid, intro="하조대 해변 도보 3분 거리의 펜션입니다."):
|
||||
await _verified_facts(client, h, pid, {"intro": intro})
|
||||
job_id = (await client.post(f"/v1/place/{pid}/site/build", headers=h, json={"publish": True})).json()["job_id"]
|
||||
await _run()
|
||||
return (await client.get(f"/v1/job/{job_id}", headers=h)).json()["job"]["result"]
|
||||
|
||||
|
||||
async def test_rollback_to_earlier_version(auth_headers, client, db_engine):
|
||||
"""검증: v1 발행 → v2 발행 → v1 로 롤백.
|
||||
기대결과: 공개 버전이 다시 v1 이 된다. 롤백은 새 site_versions 행을 만들지 않는다."""
|
||||
h = await auth_headers("u1")
|
||||
pid = await _place(client, h)
|
||||
await _approved_media(db_engine, pid)
|
||||
|
||||
r1 = await _publish(client, h, pid, "v1 소개 — 하조대 해변 도보 3분 거리의 펜션입니다.")
|
||||
assert r1["published"] is True
|
||||
r2 = await _publish(client, h, pid, "v2 소개 — 리모델링을 마친 하조대 앞 오션뷰 펜션입니다.")
|
||||
assert r2["published"] is True
|
||||
assert r2["version"] == r1["version"] + 1
|
||||
|
||||
job_id = (await client.post(
|
||||
f"/v1/place/{pid}/site/version/rollback", headers=h, json={"target_version": r1["version"]}
|
||||
)).json()["job_id"]
|
||||
await _run()
|
||||
job = (await client.get(f"/v1/job/{job_id}", headers=h)).json()["job"]
|
||||
assert job["status"] == JobStatus.DONE.value, job.get("last_error")
|
||||
result = job["result"]
|
||||
assert result["rolled_back"] is True, result.get("error")
|
||||
assert result["target_version"] == r1["version"]
|
||||
|
||||
site = (await client.get(f"/v1/place/{pid}/site", headers=h)).json()
|
||||
assert site["site"]["status"] == SiteStatus.PUBLISHED.value
|
||||
assert site["current_version"]["version"] == r1["version"]
|
||||
assert site["current_version"]["build_status"] == BuildStatus.BUILT.value
|
||||
|
||||
versions = (await client.get(f"/v1/place/{pid}/site/version/list", headers=h)).json()["versions"]
|
||||
# ★ 롤백은 새 버전을 만들지 않는다 — v1·v2 두 개뿐이어야 한다.
|
||||
assert len(versions) == 2
|
||||
|
||||
|
||||
async def test_rollback_rejects_current_version(auth_headers, client, db_engine):
|
||||
"""검증: 지금 공개 중인 버전으로 롤백을 시도한다.
|
||||
기대결과: ★ 성공으로 끝나지 않는다(last_error) — 의미 없는 재굽기를 하지 않는다.
|
||||
(BuildAborted 와 같은 패턴 — 재시도해도 소용없는 중단이라 PENDING(백오프)으로 남고,
|
||||
소진되면 DEAD 된다. 여기서 보는 건 "그래도 site 상태는 안 바뀐다"는 것이다.)"""
|
||||
h = await auth_headers("u1")
|
||||
pid = await _place(client, h)
|
||||
await _approved_media(db_engine, pid)
|
||||
r1 = await _publish(client, h, pid)
|
||||
|
||||
job_id = (await client.post(
|
||||
f"/v1/place/{pid}/site/version/rollback", headers=h, json={"target_version": r1["version"]}
|
||||
)).json()["job_id"]
|
||||
await _run()
|
||||
job = (await client.get(f"/v1/job/{job_id}", headers=h)).json()["job"]
|
||||
assert job["status"] != JobStatus.DONE.value
|
||||
assert "이미" in (job.get("last_error") or "")
|
||||
|
||||
site = (await client.get(f"/v1/place/{pid}/site", headers=h)).json()
|
||||
assert site["current_version"]["version"] == r1["version"]
|
||||
|
||||
|
||||
async def test_rollback_rejects_unknown_version(auth_headers, client, db_engine):
|
||||
"""검증: 존재하지 않는 버전 번호로 롤백을 시도한다.
|
||||
기대결과: ★ 잡이 성공으로 끝나지 않는다 — 공개 상태는 그대로다."""
|
||||
h = await auth_headers("u1")
|
||||
pid = await _place(client, h)
|
||||
await _approved_media(db_engine, pid)
|
||||
r1 = await _publish(client, h, pid)
|
||||
|
||||
job_id = (await client.post(
|
||||
f"/v1/place/{pid}/site/version/rollback", headers=h, json={"target_version": 999}
|
||||
)).json()["job_id"]
|
||||
await _run()
|
||||
job = (await client.get(f"/v1/job/{job_id}", headers=h)).json()["job"]
|
||||
assert job["status"] != JobStatus.DONE.value
|
||||
|
||||
site = (await client.get(f"/v1/place/{pid}/site", headers=h)).json()
|
||||
assert site["site"]["status"] == SiteStatus.PUBLISHED.value
|
||||
assert site["current_version"]["version"] == r1["version"]
|
||||
|
||||
|
||||
async def test_failed_rollback_keeps_current_published_version(auth_headers, client, db_engine, monkeypatch):
|
||||
"""검증: 롤백 대상 렌더가 실패한다(구조화 데이터 불일치로 시뮬레이션).
|
||||
기대결과: ★ site.current_version_id 는 바뀌지 않는다 — 직전 공개본이 그대로 서비스된다."""
|
||||
from services import render_service
|
||||
|
||||
h = await auth_headers("u1")
|
||||
pid = await _place(client, h)
|
||||
await _approved_media(db_engine, pid)
|
||||
r1 = await _publish(client, h, pid, "v1 소개 — 하조대 해변 도보 3분 거리의 펜션입니다.")
|
||||
r2 = await _publish(client, h, pid, "v2 소개 — 리모델링을 마친 하조대 앞 오션뷰 펜션입니다.")
|
||||
site_before = (await client.get(f"/v1/place/{pid}/site", headers=h)).json()
|
||||
assert site_before["current_version"]["version"] == r2["version"]
|
||||
|
||||
async def _broken(payload_path, site_version, timeout_sec):
|
||||
raise render_service.RenderFailed("렌더러가 죽었다(시뮬레이션)")
|
||||
|
||||
monkeypatch.setattr(render_service, "render_site", _broken)
|
||||
|
||||
job_id = (await client.post(
|
||||
f"/v1/place/{pid}/site/version/rollback", headers=h, json={"target_version": r1["version"]}
|
||||
)).json()["job_id"]
|
||||
await _run()
|
||||
job = (await client.get(f"/v1/job/{job_id}", headers=h)).json()["job"]
|
||||
assert job["status"] == JobStatus.DONE.value
|
||||
assert job["result"]["rolled_back"] is False
|
||||
|
||||
site_after = (await client.get(f"/v1/place/{pid}/site", headers=h)).json()
|
||||
assert site_after["current_version"]["version"] == r2["version"], "실패했는데 공개 버전이 바뀌었다"
|
||||
@ -16,6 +16,7 @@
|
||||
JobType.BUILD ✓ services/build_service.run_build — 정적 빌드 + 발행 검수 게이트
|
||||
JobType.LOCAL_SYNC ✓ services/story_service.run_local_sync — 지역 이야기 생성(지역당 1회)
|
||||
JobType.SONG ✓ services/song_service.run_song — 이 숙소의 노래 한 곡(가사 Gemini → 작곡 Suno)
|
||||
JobType.ROLLBACK ✓ services/rollback_service.run_rollback — 예전 버전으로 공개 주소를 되돌림
|
||||
JobType.AI_CHECK → reports 모듈이 붙을 때
|
||||
"""
|
||||
|
||||
@ -82,6 +83,7 @@ def _register_builtin():
|
||||
from services.copy_service import run_copy
|
||||
from services.story_service import run_local_sync
|
||||
from services.song_service import run_song
|
||||
from services.rollback_service import run_rollback
|
||||
from services.vision_service import run_vision
|
||||
|
||||
if JobType.COLLECT.value not in HANDLERS:
|
||||
@ -96,6 +98,8 @@ def _register_builtin():
|
||||
HANDLERS[JobType.LOCAL_SYNC.value] = run_local_sync
|
||||
if JobType.SONG.value not in HANDLERS:
|
||||
HANDLERS[JobType.SONG.value] = run_song
|
||||
if JobType.ROLLBACK.value not in HANDLERS:
|
||||
HANDLERS[JobType.ROLLBACK.value] = run_rollback
|
||||
|
||||
|
||||
_register_builtin()
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useMutation,
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -20,8 +17,8 @@ import type {
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
@ -31,429 +28,576 @@ import type {
|
||||
ReqUpdateMe,
|
||||
ResLogin,
|
||||
ResMe,
|
||||
ResRefreshToken
|
||||
} from '.././model';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
ResRefreshToken,
|
||||
} from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* id/pw 로 로그인하고 JWT 토큰을 발급한다.
|
||||
* @summary 로그인
|
||||
*/
|
||||
export const login = (
|
||||
reqLogin: ReqLogin,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
reqLogin: ReqLogin,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResLogin>(
|
||||
{url: `/v1/auth/login`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqLogin, signal
|
||||
return customFetch<ResLogin>(
|
||||
{
|
||||
url: `/v1/auth/login`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqLogin,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getLoginMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["login"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getLoginMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
{ data: ReqLogin }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
const mutationKey = ['login'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return login(data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type LoginMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof login>>
|
||||
>;
|
||||
export type LoginMutationBody = ReqLogin;
|
||||
export type LoginMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof login>>, {data: ReqLogin}> = (props) => {
|
||||
const {data} = props ?? {};
|
||||
|
||||
return login(data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type LoginMutationResult = NonNullable<Awaited<ReturnType<typeof login>>>
|
||||
export type LoginMutationBody = ReqLogin
|
||||
export type LoginMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 로그인
|
||||
*/
|
||||
export const useLogin = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
{data: ReqLogin},
|
||||
TContext
|
||||
> => {
|
||||
export const useLogin = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof login>>,
|
||||
TError,
|
||||
{ data: ReqLogin },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getLoginMutationOptions(options);
|
||||
|
||||
const mutationOptions = getLoginMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* id/pw 로 가입한다. 회사(테넌트) 1개가 함께 생기고, 성공하면 곧바로 로그인 토큰을 발급한다.
|
||||
* @summary 회원가입
|
||||
*/
|
||||
export const signup = (
|
||||
reqSignup: ReqSignup,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
reqSignup: ReqSignup,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResLogin>(
|
||||
{url: `/v1/auth/signup`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqSignup, signal
|
||||
return customFetch<ResLogin>(
|
||||
{
|
||||
url: `/v1/auth/signup`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqSignup,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getSignupMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
TError,
|
||||
{ data: ReqSignup },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
TError,
|
||||
{ data: ReqSignup },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["signup"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getSignupMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
{ data: ReqSignup }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
const mutationKey = ['signup'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return signup(data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type SignupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof signup>>
|
||||
>;
|
||||
export type SignupMutationBody = ReqSignup;
|
||||
export type SignupMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof signup>>, {data: ReqSignup}> = (props) => {
|
||||
const {data} = props ?? {};
|
||||
|
||||
return signup(data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type SignupMutationResult = NonNullable<Awaited<ReturnType<typeof signup>>>
|
||||
export type SignupMutationBody = ReqSignup
|
||||
export type SignupMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 회원가입
|
||||
*/
|
||||
export const useSignup = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
TError,
|
||||
{data: ReqSignup},
|
||||
TContext
|
||||
> => {
|
||||
export const useSignup = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
TError,
|
||||
{ data: ReqSignup },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof signup>>,
|
||||
TError,
|
||||
{ data: ReqSignup },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getSignupMutationOptions(options);
|
||||
|
||||
const mutationOptions = getSignupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 구글 ID 토큰(GIS credential)을 검증하고 JWT 토큰을 발급한다. 처음 온 계정은 그 자리에서 만든다.
|
||||
* @summary 구글 로그인
|
||||
*/
|
||||
export const googleLogin = (
|
||||
reqGoogleLogin: ReqGoogleLogin,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
reqGoogleLogin: ReqGoogleLogin,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResLogin>(
|
||||
{url: `/v1/auth/google`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqGoogleLogin, signal
|
||||
return customFetch<ResLogin>(
|
||||
{
|
||||
url: `/v1/auth/google`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqGoogleLogin,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getGoogleLoginMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
TError,
|
||||
{ data: ReqGoogleLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
TError,
|
||||
{ data: ReqGoogleLogin },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["googleLogin"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getGoogleLoginMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
{ data: ReqGoogleLogin }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
const mutationKey = ['googleLogin'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return googleLogin(data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type GoogleLoginMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof googleLogin>>
|
||||
>;
|
||||
export type GoogleLoginMutationBody = ReqGoogleLogin;
|
||||
export type GoogleLoginMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof googleLogin>>, {data: ReqGoogleLogin}> = (props) => {
|
||||
const {data} = props ?? {};
|
||||
|
||||
return googleLogin(data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type GoogleLoginMutationResult = NonNullable<Awaited<ReturnType<typeof googleLogin>>>
|
||||
export type GoogleLoginMutationBody = ReqGoogleLogin
|
||||
export type GoogleLoginMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 구글 로그인
|
||||
*/
|
||||
export const useGoogleLogin = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
TError,
|
||||
{data: ReqGoogleLogin},
|
||||
TContext
|
||||
> => {
|
||||
export const useGoogleLogin = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
TError,
|
||||
{ data: ReqGoogleLogin },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof googleLogin>>,
|
||||
TError,
|
||||
{ data: ReqGoogleLogin },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getGoogleLoginMutationOptions(options);
|
||||
|
||||
const mutationOptions = getGoogleLoginMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* refresh 토큰으로 access 토큰을 재발급한다.
|
||||
* @summary 액세스 토큰 갱신
|
||||
*/
|
||||
export const refreshToken = (
|
||||
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResRefreshToken>(
|
||||
{url: `/v1/auth/refresh_token`, method: 'POST', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResRefreshToken>(
|
||||
{ url: `/v1/auth/refresh_token`, method: "POST", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getRefreshTokenMutationOptions = <
|
||||
TError = void,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["refreshToken"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getRefreshTokenMutationOptions = <TError = void,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
void
|
||||
> = () => {
|
||||
return refreshToken(requestOptions);
|
||||
};
|
||||
|
||||
const mutationKey = ['refreshToken'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
|
||||
export type RefreshTokenMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof refreshToken>>
|
||||
>;
|
||||
|
||||
export type RefreshTokenMutationError = void;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof refreshToken>>, void> = () => {
|
||||
|
||||
|
||||
return refreshToken(requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type RefreshTokenMutationResult = NonNullable<Awaited<ReturnType<typeof refreshToken>>>
|
||||
|
||||
export type RefreshTokenMutationError = void
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 액세스 토큰 갱신
|
||||
*/
|
||||
export const useRefreshToken = <TError = void,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
export const useRefreshToken = <TError = void, TContext = unknown>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof refreshToken>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getRefreshTokenMutationOptions(options);
|
||||
|
||||
const mutationOptions = getRefreshTokenMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 유효한 access 토큰이 있어야 호출 가능. 토큰의 유저+회사 정보를 반환한다.
|
||||
* @summary 내 정보
|
||||
*/
|
||||
export const me = (
|
||||
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResMe>(
|
||||
{url: `/v1/auth/me`, method: 'GET', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return customFetch<ResMe>(
|
||||
{ url: `/v1/auth/me`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getMeQueryKey = () => {
|
||||
return [
|
||||
`/v1/auth/me`
|
||||
] as const;
|
||||
}
|
||||
return [`/v1/auth/me`] as const;
|
||||
};
|
||||
|
||||
|
||||
export const getMeQueryOptions = <TData = Awaited<ReturnType<typeof me>>, TError = void>( options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
export const getMeQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof me>>,
|
||||
TError = void,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
const queryKey = queryOptions?.queryKey ?? getMeQueryKey();
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getMeQueryKey();
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof me>>> = ({ signal }) =>
|
||||
me(requestOptions, signal);
|
||||
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof me>>> = ({ signal }) => me(requestOptions, signal);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type MeQueryResult = NonNullable<Awaited<ReturnType<typeof me>>>
|
||||
export type MeQueryError = void
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof me>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type MeQueryResult = NonNullable<Awaited<ReturnType<typeof me>>>;
|
||||
export type MeQueryError = void;
|
||||
|
||||
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>> & Pick<
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof me>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof me>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>> & Pick<
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof me>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof me>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 내 정보
|
||||
*/
|
||||
|
||||
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getMeQueryOptions(options);
|
||||
|
||||
const queryOptions = getMeQueryOptions(options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 본인 이름/이메일/연락처/비밀번호를 수정한다(권한·소속·ID 변경 불가).
|
||||
* @summary 내 정보 수정
|
||||
*/
|
||||
export const updateMe = (
|
||||
reqUpdateMe: ReqUpdateMe,
|
||||
options?: SecondParameter<typeof customFetch>,) => {
|
||||
|
||||
|
||||
return customFetch<ResMe>(
|
||||
{url: `/v1/auth/me`, method: 'PATCH',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqUpdateMe
|
||||
reqUpdateMe: ReqUpdateMe,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
) => {
|
||||
return customFetch<ResMe>(
|
||||
{
|
||||
url: `/v1/auth/me`,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpdateMe,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getUpdateMeMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
TError,
|
||||
{ data: ReqUpdateMe },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
TError,
|
||||
{ data: ReqUpdateMe },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["updateMe"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getUpdateMeMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
{ data: ReqUpdateMe }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
const mutationKey = ['updateMe'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return updateMe(data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateMeMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateMe>>
|
||||
>;
|
||||
export type UpdateMeMutationBody = ReqUpdateMe;
|
||||
export type UpdateMeMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof updateMe>>, {data: ReqUpdateMe}> = (props) => {
|
||||
const {data} = props ?? {};
|
||||
|
||||
return updateMe(data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type UpdateMeMutationResult = NonNullable<Awaited<ReturnType<typeof updateMe>>>
|
||||
export type UpdateMeMutationBody = ReqUpdateMe
|
||||
export type UpdateMeMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 내 정보 수정
|
||||
*/
|
||||
export const useUpdateMe = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
TError,
|
||||
{data: ReqUpdateMe},
|
||||
TContext
|
||||
> => {
|
||||
export const useUpdateMe = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
TError,
|
||||
{ data: ReqUpdateMe },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateMe>>,
|
||||
TError,
|
||||
{ data: ReqUpdateMe },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateMeMutationOptions(options);
|
||||
|
||||
const mutationOptions = getUpdateMeMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
|
||||
@ -4,9 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -16,105 +14,163 @@ import type {
|
||||
QueryKey,
|
||||
UndefinedInitialDataOptions,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @summary Healthz
|
||||
*/
|
||||
export const healthzHealthzGet = (
|
||||
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<unknown>(
|
||||
{url: `/healthz`, method: 'GET', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return customFetch<unknown>(
|
||||
{ url: `/healthz`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getHealthzHealthzGetQueryKey = () => {
|
||||
return [
|
||||
`/healthz`
|
||||
] as const;
|
||||
}
|
||||
return [`/healthz`] as const;
|
||||
};
|
||||
|
||||
|
||||
export const getHealthzHealthzGetQueryOptions = <TData = Awaited<ReturnType<typeof healthzHealthzGet>>, TError = void>( options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
export const getHealthzHealthzGetQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError = void,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
const queryKey = queryOptions?.queryKey ?? getHealthzHealthzGetQueryKey();
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getHealthzHealthzGetQueryKey();
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>
|
||||
> = ({ signal }) => healthzHealthzGet(requestOptions, signal);
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof healthzHealthzGet>>> = ({ signal }) => healthzHealthzGet(requestOptions, signal);
|
||||
export type HealthzHealthzGetQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>
|
||||
>;
|
||||
export type HealthzHealthzGetQueryError = void;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type HealthzHealthzGetQueryResult = NonNullable<Awaited<ReturnType<typeof healthzHealthzGet>>>
|
||||
export type HealthzHealthzGetQueryError = void
|
||||
|
||||
|
||||
export function useHealthzHealthzGet<TData = Awaited<ReturnType<typeof healthzHealthzGet>>, TError = void>(
|
||||
options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData>> & Pick<
|
||||
export function useHealthzHealthzGet<
|
||||
TData = Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError = void,
|
||||
>(
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useHealthzHealthzGet<TData = Awaited<ReturnType<typeof healthzHealthzGet>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useHealthzHealthzGet<
|
||||
TData = Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useHealthzHealthzGet<TData = Awaited<ReturnType<typeof healthzHealthzGet>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useHealthzHealthzGet<
|
||||
TData = Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary Healthz
|
||||
*/
|
||||
|
||||
export function useHealthzHealthzGet<TData = Awaited<ReturnType<typeof healthzHealthzGet>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof healthzHealthzGet>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useHealthzHealthzGet<
|
||||
TData = Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof healthzHealthzGet>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getHealthzHealthzGetQueryOptions(options);
|
||||
|
||||
const queryOptions = getHealthzHealthzGetQueryOptions(options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useMutation,
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -20,8 +17,8 @@ import type {
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
@ -32,407 +29,595 @@ import type {
|
||||
ResCategorySchema,
|
||||
ResExtractFacts,
|
||||
ResFact,
|
||||
ResFactList
|
||||
} from '.././model';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
ResFactList,
|
||||
} from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 이 사업장의 업종에 존재하는 fact 필드 정의. 관리 화면이 입력 폼을 그리는 데 쓴다.
|
||||
* @summary 업종 fact 스키마
|
||||
*/
|
||||
export const getSchema = (
|
||||
placeId: string,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResCategorySchema>(
|
||||
{url: `/v1/place/${placeId}/fact/schema`, method: 'GET', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResCategorySchema>(
|
||||
{ url: `/v1/place/${placeId}/fact/schema`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getGetSchemaQueryKey = (placeId?: string) => {
|
||||
return [`/v1/place/${placeId}/fact/schema`] as const;
|
||||
};
|
||||
|
||||
|
||||
export const getGetSchemaQueryKey = (placeId?: string,) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/fact/schema`
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getGetSchemaQueryOptions = <TData = Awaited<ReturnType<typeof getSchema>>, TError = void | HTTPValidationError>(placeId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
export const getGetSchemaQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getSchema>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
const queryKey = queryOptions?.queryKey ?? getGetSchemaQueryKey(placeId);
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetSchemaQueryKey(placeId);
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getSchema>>> = ({
|
||||
signal,
|
||||
}) => getSchema(placeId, requestOptions, signal);
|
||||
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!placeId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getSchema>>> = ({ signal }) => getSchema(placeId, requestOptions, signal);
|
||||
export type GetSchemaQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getSchema>>
|
||||
>;
|
||||
export type GetSchemaQueryError = void | HTTPValidationError;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(placeId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type GetSchemaQueryResult = NonNullable<Awaited<ReturnType<typeof getSchema>>>
|
||||
export type GetSchemaQueryError = void | HTTPValidationError
|
||||
|
||||
|
||||
export function useGetSchema<TData = Awaited<ReturnType<typeof getSchema>>, TError = void | HTTPValidationError>(
|
||||
placeId: string, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>> & Pick<
|
||||
export function useGetSchema<
|
||||
TData = Awaited<ReturnType<typeof getSchema>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getSchema>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getSchema>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useGetSchema<TData = Awaited<ReturnType<typeof getSchema>>, TError = void | HTTPValidationError>(
|
||||
placeId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetSchema<
|
||||
TData = Awaited<ReturnType<typeof getSchema>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getSchema>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getSchema>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useGetSchema<TData = Awaited<ReturnType<typeof getSchema>>, TError = void | HTTPValidationError>(
|
||||
placeId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetSchema<
|
||||
TData = Awaited<ReturnType<typeof getSchema>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 업종 fact 스키마
|
||||
*/
|
||||
|
||||
export function useGetSchema<TData = Awaited<ReturnType<typeof getSchema>>, TError = void | HTTPValidationError>(
|
||||
placeId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useGetSchema<
|
||||
TData = Awaited<ReturnType<typeof getSchema>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getSchema>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getGetSchemaQueryOptions(placeId, options);
|
||||
|
||||
const queryOptions = getGetSchemaQueryOptions(placeId,options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* publishable_only=true 면 ★ 사이트에 나갈 수 있는 것(VERIFIED·CORRECTED)만.
|
||||
* @summary fact 목록
|
||||
*/
|
||||
export const listFacts = (
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFactList>(
|
||||
{url: `/v1/place/${placeId}/fact/list`, method: 'GET',
|
||||
params, signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResFactList>(
|
||||
{ url: `/v1/place/${placeId}/fact/list`, method: "GET", params, signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getListFactsQueryKey = (placeId?: string,
|
||||
params?: ListFactsParams,) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/fact/list`, ...(params ? [params]: [])
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getListFactsQueryOptions = <TData = Awaited<ReturnType<typeof listFacts>>, TError = void | HTTPValidationError>(placeId: string,
|
||||
params?: ListFactsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
export const getListFactsQueryKey = (
|
||||
placeId?: string,
|
||||
params?: ListFactsParams,
|
||||
) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/fact/list`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
export const getListFactsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listFacts>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListFactsQueryKey(placeId,params);
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListFactsQueryKey(placeId, params);
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listFacts>>> = ({
|
||||
signal,
|
||||
}) => listFacts(placeId, params, requestOptions, signal);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listFacts>>> = ({ signal }) => listFacts(placeId,params, requestOptions, signal);
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!placeId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export type ListFactsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listFacts>>
|
||||
>;
|
||||
export type ListFactsQueryError = void | HTTPValidationError;
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(placeId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type ListFactsQueryResult = NonNullable<Awaited<ReturnType<typeof listFacts>>>
|
||||
export type ListFactsQueryError = void | HTTPValidationError
|
||||
|
||||
|
||||
export function useListFacts<TData = Awaited<ReturnType<typeof listFacts>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params: undefined | ListFactsParams, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>> & Pick<
|
||||
export function useListFacts<
|
||||
TData = Awaited<ReturnType<typeof listFacts>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params: undefined | ListFactsParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listFacts>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listFacts>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListFacts<TData = Awaited<ReturnType<typeof listFacts>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListFacts<
|
||||
TData = Awaited<ReturnType<typeof listFacts>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listFacts>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listFacts>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListFacts<TData = Awaited<ReturnType<typeof listFacts>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListFacts<
|
||||
TData = Awaited<ReturnType<typeof listFacts>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary fact 목록
|
||||
*/
|
||||
|
||||
export function useListFacts<TData = Awaited<ReturnType<typeof listFacts>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useListFacts<
|
||||
TData = Awaited<ReturnType<typeof listFacts>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFactsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFacts>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListFactsQueryOptions(placeId, params, options);
|
||||
|
||||
const queryOptions = getListFactsQueryOptions(placeId,params,options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 같은 key 의 활성 fact 는 EXPIRED 로 밀리고 새 행이 들어간다(값 이력 보존). ★ 자동 수집(api/crawl/llm)은 사장님 수정본(CORRECTED)을 덮어쓸 수 없다.
|
||||
* @summary fact 기록
|
||||
*/
|
||||
export const upsertFact = (
|
||||
placeId: string,
|
||||
reqUpsertFact: ReqUpsertFact,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
reqUpsertFact: ReqUpsertFact,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFact>(
|
||||
{url: `/v1/place/${placeId}/fact`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqUpsertFact, signal
|
||||
return customFetch<ResFact>(
|
||||
{
|
||||
url: `/v1/place/${placeId}/fact`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqUpsertFact,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getUpsertFactMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqUpsertFact },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqUpsertFact },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["upsertFact"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getUpsertFactMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof upsertFact>>, TError,{placeId: string;data: ReqUpsertFact}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof upsertFact>>, TError,{placeId: string;data: ReqUpsertFact}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
{ placeId: string; data: ReqUpsertFact }
|
||||
> = (props) => {
|
||||
const { placeId, data } = props ?? {};
|
||||
|
||||
const mutationKey = ['upsertFact'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return upsertFact(placeId, data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpsertFactMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof upsertFact>>
|
||||
>;
|
||||
export type UpsertFactMutationBody = ReqUpsertFact;
|
||||
export type UpsertFactMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof upsertFact>>, {placeId: string;data: ReqUpsertFact}> = (props) => {
|
||||
const {placeId,data} = props ?? {};
|
||||
|
||||
return upsertFact(placeId,data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type UpsertFactMutationResult = NonNullable<Awaited<ReturnType<typeof upsertFact>>>
|
||||
export type UpsertFactMutationBody = ReqUpsertFact
|
||||
export type UpsertFactMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary fact 기록
|
||||
*/
|
||||
export const useUpsertFact = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof upsertFact>>, TError,{placeId: string;data: ReqUpsertFact}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
TError,
|
||||
{placeId: string;data: ReqUpsertFact},
|
||||
TContext
|
||||
> => {
|
||||
export const useUpsertFact = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqUpsertFact },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof upsertFact>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqUpsertFact },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpsertFactMutationOptions(options);
|
||||
|
||||
const mutationOptions = getUpsertFactMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 허용 전이만 통과한다(FACT_STATUS_TRANSITIONS). CORRECTED 로 갈 때는 value 가 필수다.
|
||||
* @summary 검증 상태 전이
|
||||
*/
|
||||
export const transitionFact = (
|
||||
placeId: string,
|
||||
factId: string,
|
||||
reqTransitionFact: ReqTransitionFact,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
factId: string,
|
||||
reqTransitionFact: ReqTransitionFact,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFact>(
|
||||
{url: `/v1/place/${placeId}/fact/${factId}/transition`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqTransitionFact, signal
|
||||
return customFetch<ResFact>(
|
||||
{
|
||||
url: `/v1/place/${placeId}/fact/${factId}/transition`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqTransitionFact,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getTransitionFactMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
TError,
|
||||
{ placeId: string; factId: string; data: ReqTransitionFact },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
TError,
|
||||
{ placeId: string; factId: string; data: ReqTransitionFact },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["transitionFact"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getTransitionFactMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof transitionFact>>, TError,{placeId: string;factId: string;data: ReqTransitionFact}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof transitionFact>>, TError,{placeId: string;factId: string;data: ReqTransitionFact}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
{ placeId: string; factId: string; data: ReqTransitionFact }
|
||||
> = (props) => {
|
||||
const { placeId, factId, data } = props ?? {};
|
||||
|
||||
const mutationKey = ['transitionFact'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return transitionFact(placeId, factId, data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type TransitionFactMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof transitionFact>>
|
||||
>;
|
||||
export type TransitionFactMutationBody = ReqTransitionFact;
|
||||
export type TransitionFactMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof transitionFact>>, {placeId: string;factId: string;data: ReqTransitionFact}> = (props) => {
|
||||
const {placeId,factId,data} = props ?? {};
|
||||
|
||||
return transitionFact(placeId,factId,data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type TransitionFactMutationResult = NonNullable<Awaited<ReturnType<typeof transitionFact>>>
|
||||
export type TransitionFactMutationBody = ReqTransitionFact
|
||||
export type TransitionFactMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 검증 상태 전이
|
||||
*/
|
||||
export const useTransitionFact = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof transitionFact>>, TError,{placeId: string;factId: string;data: ReqTransitionFact}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
TError,
|
||||
{placeId: string;factId: string;data: ReqTransitionFact},
|
||||
TContext
|
||||
> => {
|
||||
export const useTransitionFact = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
TError,
|
||||
{ placeId: string; factId: string; data: ReqTransitionFact },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof transitionFact>>,
|
||||
TError,
|
||||
{ placeId: string; factId: string; data: ReqTransitionFact },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getTransitionFactMutationOptions(options);
|
||||
|
||||
const mutationOptions = getTransitionFactMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 사장님이 붙여넣은 소개글·안내문에서 업종 스키마 fact 를 뽑는다. ★ 뽑은 값의 **근거 문장이 원문에 실제로 있는지 대조**해 통과한 것만 저장한다. 저장된 값은 전부 후보(UNVERIFIED)라 사장님이 확인해야 사이트에 나간다. 버려진 항목은 사유와 함께 돌려준다.
|
||||
* @summary 붙여넣은 텍스트에서 fact 추출
|
||||
*/
|
||||
export const extractFactsFromText = (
|
||||
placeId: string,
|
||||
reqExtractFacts: ReqExtractFacts,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
reqExtractFacts: ReqExtractFacts,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResExtractFacts>(
|
||||
{url: `/v1/place/${placeId}/fact/extract`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqExtractFacts, signal
|
||||
return customFetch<ResExtractFacts>(
|
||||
{
|
||||
url: `/v1/place/${placeId}/fact/extract`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqExtractFacts,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getExtractFactsFromTextMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqExtractFacts },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqExtractFacts },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["extractFactsFromText"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getExtractFactsFromTextMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof extractFactsFromText>>, TError,{placeId: string;data: ReqExtractFacts}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof extractFactsFromText>>, TError,{placeId: string;data: ReqExtractFacts}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
{ placeId: string; data: ReqExtractFacts }
|
||||
> = (props) => {
|
||||
const { placeId, data } = props ?? {};
|
||||
|
||||
const mutationKey = ['extractFactsFromText'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return extractFactsFromText(placeId, data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ExtractFactsFromTextMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>
|
||||
>;
|
||||
export type ExtractFactsFromTextMutationBody = ReqExtractFacts;
|
||||
export type ExtractFactsFromTextMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof extractFactsFromText>>, {placeId: string;data: ReqExtractFacts}> = (props) => {
|
||||
const {placeId,data} = props ?? {};
|
||||
|
||||
return extractFactsFromText(placeId,data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type ExtractFactsFromTextMutationResult = NonNullable<Awaited<ReturnType<typeof extractFactsFromText>>>
|
||||
export type ExtractFactsFromTextMutationBody = ReqExtractFacts
|
||||
export type ExtractFactsFromTextMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 붙여넣은 텍스트에서 fact 추출
|
||||
*/
|
||||
export const useExtractFactsFromText = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof extractFactsFromText>>, TError,{placeId: string;data: ReqExtractFacts}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
TError,
|
||||
{placeId: string;data: ReqExtractFacts},
|
||||
TContext
|
||||
> => {
|
||||
export const useExtractFactsFromText = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqExtractFacts },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof extractFactsFromText>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqExtractFacts },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getExtractFactsFromTextMutationOptions(options);
|
||||
|
||||
const mutationOptions = getExtractFactsFromTextMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useMutation,
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -20,8 +17,8 @@ import type {
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
@ -29,248 +26,356 @@ import type {
|
||||
ReqCreateFaq,
|
||||
ReqTransitionFaq,
|
||||
ResFaq,
|
||||
ResFaqList
|
||||
} from '.././model';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
ResFaqList,
|
||||
} from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* publishable_only=true 면 ★ 사이트에 나갈 수 있는 것(VERIFIED·CORRECTED)만. 기본은 승인 대기(UNVERIFIED)까지 함께 준다 — 승인할 대상을 봐야 하기 때문이다.
|
||||
* @summary FAQ 목록
|
||||
*/
|
||||
export const listFaqs = (
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFaqList>(
|
||||
{url: `/v1/place/${placeId}/faq/list`, method: 'GET',
|
||||
params, signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResFaqList>(
|
||||
{ url: `/v1/place/${placeId}/faq/list`, method: "GET", params, signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getListFaqsQueryKey = (placeId?: string,
|
||||
params?: ListFaqsParams,) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/faq/list`, ...(params ? [params]: [])
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getListFaqsQueryOptions = <TData = Awaited<ReturnType<typeof listFaqs>>, TError = void | HTTPValidationError>(placeId: string,
|
||||
params?: ListFaqsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
export const getListFaqsQueryKey = (
|
||||
placeId?: string,
|
||||
params?: ListFaqsParams,
|
||||
) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/faq/list`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
export const getListFaqsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListFaqsQueryKey(placeId,params);
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListFaqsQueryKey(placeId, params);
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listFaqs>>> = ({
|
||||
signal,
|
||||
}) => listFaqs(placeId, params, requestOptions, signal);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listFaqs>>> = ({ signal }) => listFaqs(placeId,params, requestOptions, signal);
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!placeId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export type ListFaqsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listFaqs>>
|
||||
>;
|
||||
export type ListFaqsQueryError = void | HTTPValidationError;
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(placeId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type ListFaqsQueryResult = NonNullable<Awaited<ReturnType<typeof listFaqs>>>
|
||||
export type ListFaqsQueryError = void | HTTPValidationError
|
||||
|
||||
|
||||
export function useListFaqs<TData = Awaited<ReturnType<typeof listFaqs>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params: undefined | ListFaqsParams, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>> & Pick<
|
||||
export function useListFaqs<
|
||||
TData = Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params: undefined | ListFaqsParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listFaqs>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListFaqs<TData = Awaited<ReturnType<typeof listFaqs>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListFaqs<
|
||||
TData = Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listFaqs>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListFaqs<TData = Awaited<ReturnType<typeof listFaqs>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListFaqs<
|
||||
TData = Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary FAQ 목록
|
||||
*/
|
||||
|
||||
export function useListFaqs<TData = Awaited<ReturnType<typeof listFaqs>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useListFaqs<
|
||||
TData = Awaited<ReturnType<typeof listFaqs>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListFaqsParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listFaqs>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListFaqsQueryOptions(placeId, params, options);
|
||||
|
||||
const queryOptions = getListFaqsQueryOptions(placeId,params,options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 사장님이 직접 쓴 FAQ. fact 의 owner 입력과 같이 바로 노출값(VERIFIED)이 된다 — 사람이 곧 출처다. 출처(generated_by)는 서버가 OWNER 로 고정한다.
|
||||
* @summary FAQ 직접 추가
|
||||
*/
|
||||
export const createFaq = (
|
||||
placeId: string,
|
||||
reqCreateFaq: ReqCreateFaq,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
reqCreateFaq: ReqCreateFaq,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFaq>(
|
||||
{url: `/v1/place/${placeId}/faq`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqCreateFaq, signal
|
||||
return customFetch<ResFaq>(
|
||||
{
|
||||
url: `/v1/place/${placeId}/faq`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqCreateFaq,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getCreateFaqMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqCreateFaq },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqCreateFaq },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["createFaq"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getCreateFaqMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof createFaq>>, TError,{placeId: string;data: ReqCreateFaq}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof createFaq>>, TError,{placeId: string;data: ReqCreateFaq}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
{ placeId: string; data: ReqCreateFaq }
|
||||
> = (props) => {
|
||||
const { placeId, data } = props ?? {};
|
||||
|
||||
const mutationKey = ['createFaq'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return createFaq(placeId, data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateFaqMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createFaq>>
|
||||
>;
|
||||
export type CreateFaqMutationBody = ReqCreateFaq;
|
||||
export type CreateFaqMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof createFaq>>, {placeId: string;data: ReqCreateFaq}> = (props) => {
|
||||
const {placeId,data} = props ?? {};
|
||||
|
||||
return createFaq(placeId,data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type CreateFaqMutationResult = NonNullable<Awaited<ReturnType<typeof createFaq>>>
|
||||
export type CreateFaqMutationBody = ReqCreateFaq
|
||||
export type CreateFaqMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary FAQ 직접 추가
|
||||
*/
|
||||
export const useCreateFaq = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof createFaq>>, TError,{placeId: string;data: ReqCreateFaq}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
TError,
|
||||
{placeId: string;data: ReqCreateFaq},
|
||||
TContext
|
||||
> => {
|
||||
export const useCreateFaq = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqCreateFaq },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createFaq>>,
|
||||
TError,
|
||||
{ placeId: string; data: ReqCreateFaq },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateFaqMutationOptions(options);
|
||||
|
||||
const mutationOptions = getCreateFaqMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
/**
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* 허용 전이만 통과한다(FACT_STATUS_TRANSITIONS — fact 와 같은 표). CORRECTED 로 갈 때는 고친 question/answer 중 하나 이상이 필요하고, 그 뒤로는 ★ 잠긴 상태라 재생성이 덮어쓰지 못한다.
|
||||
* @summary 검증 상태 전이(승인·정정·반려)
|
||||
*/
|
||||
export const transitionFaq = (
|
||||
placeId: string,
|
||||
faqId: string,
|
||||
reqTransitionFaq: ReqTransitionFaq,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
faqId: string,
|
||||
reqTransitionFaq: ReqTransitionFaq,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResFaq>(
|
||||
{url: `/v1/place/${placeId}/faq/${faqId}/transition`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: reqTransitionFaq, signal
|
||||
return customFetch<ResFaq>(
|
||||
{
|
||||
url: `/v1/place/${placeId}/faq/${faqId}/transition`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: reqTransitionFaq,
|
||||
signal,
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getTransitionFaqMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
TError,
|
||||
{ placeId: string; faqId: string; data: ReqTransitionFaq },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
TError,
|
||||
{ placeId: string; faqId: string; data: ReqTransitionFaq },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["transitionFaq"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getTransitionFaqMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof transitionFaq>>, TError,{placeId: string;faqId: string;data: ReqTransitionFaq}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof transitionFaq>>, TError,{placeId: string;faqId: string;data: ReqTransitionFaq}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
{ placeId: string; faqId: string; data: ReqTransitionFaq }
|
||||
> = (props) => {
|
||||
const { placeId, faqId, data } = props ?? {};
|
||||
|
||||
const mutationKey = ['transitionFaq'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return transitionFaq(placeId, faqId, data, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type TransitionFaqMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof transitionFaq>>
|
||||
>;
|
||||
export type TransitionFaqMutationBody = ReqTransitionFaq;
|
||||
export type TransitionFaqMutationError = void | HTTPValidationError;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof transitionFaq>>, {placeId: string;faqId: string;data: ReqTransitionFaq}> = (props) => {
|
||||
const {placeId,faqId,data} = props ?? {};
|
||||
|
||||
return transitionFaq(placeId,faqId,data,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type TransitionFaqMutationResult = NonNullable<Awaited<ReturnType<typeof transitionFaq>>>
|
||||
export type TransitionFaqMutationBody = ReqTransitionFaq
|
||||
export type TransitionFaqMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary 검증 상태 전이(승인·정정·반려)
|
||||
*/
|
||||
export const useTransitionFaq = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof transitionFaq>>, TError,{placeId: string;faqId: string;data: ReqTransitionFaq}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
TError,
|
||||
{placeId: string;faqId: string;data: ReqTransitionFaq},
|
||||
TContext
|
||||
> => {
|
||||
export const useTransitionFaq = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
TError,
|
||||
{ placeId: string; faqId: string; data: ReqTransitionFaq },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof transitionFaq>>,
|
||||
TError,
|
||||
{ placeId: string; faqId: string; data: ReqTransitionFaq },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getTransitionFaqMutationOptions(options);
|
||||
|
||||
const mutationOptions = getTransitionFaqMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
|
||||
166
solution/frontend/src/api/generated/image/image.ts
Normal file
166
solution/frontend/src/api/generated/image/image.ts
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
DefinedUseQueryResult,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UndefinedInitialDataOptions,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type { HTTPValidationError, RelayParams } from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* 인증을 요구하지 않는다. 발행본은 로그인 없이 열리는 정적 페이지이고, 여기서 나가는
|
||||
것은 **그 페이지가 이미 화면에 싣고 있는 사진**뿐이다(allowlist 가 그걸 보장한다).
|
||||
* @summary 사진 중계 — 캔버스 오염을 피하려고 같은 오리진으로 흘려보낸다
|
||||
*/
|
||||
export const relay = (
|
||||
params: RelayParams,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return customFetch<unknown>(
|
||||
{ url: `/v1/image/relay`, method: "GET", params, signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getRelayQueryKey = (params?: RelayParams) => {
|
||||
return [`/v1/image/relay`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getRelayQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof relay>>,
|
||||
TError = HTTPValidationError,
|
||||
>(
|
||||
params: RelayParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof relay>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getRelayQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof relay>>> = ({
|
||||
signal,
|
||||
}) => relay(params, requestOptions, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof relay>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type RelayQueryResult = NonNullable<Awaited<ReturnType<typeof relay>>>;
|
||||
export type RelayQueryError = HTTPValidationError;
|
||||
|
||||
export function useRelay<
|
||||
TData = Awaited<ReturnType<typeof relay>>,
|
||||
TError = HTTPValidationError,
|
||||
>(
|
||||
params: RelayParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof relay>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof relay>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof relay>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useRelay<
|
||||
TData = Awaited<ReturnType<typeof relay>>,
|
||||
TError = HTTPValidationError,
|
||||
>(
|
||||
params: RelayParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof relay>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof relay>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof relay>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useRelay<
|
||||
TData = Awaited<ReturnType<typeof relay>>,
|
||||
TError = HTTPValidationError,
|
||||
>(
|
||||
params: RelayParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof relay>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 사진 중계 — 캔버스 오염을 피하려고 같은 오리진으로 흘려보낸다
|
||||
*/
|
||||
|
||||
export function useRelay<
|
||||
TData = Awaited<ReturnType<typeof relay>>,
|
||||
TError = HTTPValidationError,
|
||||
>(
|
||||
params: RelayParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof relay>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getRelayQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
@ -4,10 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useMutation,
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -20,268 +17,374 @@ import type {
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
ResJob,
|
||||
ResJobOps
|
||||
} from '.././model';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
import type { HTTPValidationError, ResJob, ResJobOps } from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 상태별 카운트 + 큐 지연 + 최근 1시간 DEAD + 좀비(stuck). 내부 운영 전용.
|
||||
* @summary 큐 운영 스냅샷
|
||||
*/
|
||||
export const jobOps = (
|
||||
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResJobOps>(
|
||||
{url: `/v1/job/ops`, method: 'GET', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return customFetch<ResJobOps>(
|
||||
{ url: `/v1/job/ops`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getJobOpsQueryKey = () => {
|
||||
return [
|
||||
`/v1/job/ops`
|
||||
] as const;
|
||||
}
|
||||
return [`/v1/job/ops`] as const;
|
||||
};
|
||||
|
||||
|
||||
export const getJobOpsQueryOptions = <TData = Awaited<ReturnType<typeof jobOps>>, TError = void>( options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
export const getJobOpsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof jobOps>>,
|
||||
TError = void,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
const queryKey = queryOptions?.queryKey ?? getJobOpsQueryKey();
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getJobOpsQueryKey();
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof jobOps>>> = ({
|
||||
signal,
|
||||
}) => jobOps(requestOptions, signal);
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof jobOps>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof jobOps>>> = ({ signal }) => jobOps(requestOptions, signal);
|
||||
export type JobOpsQueryResult = NonNullable<Awaited<ReturnType<typeof jobOps>>>;
|
||||
export type JobOpsQueryError = void;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type JobOpsQueryResult = NonNullable<Awaited<ReturnType<typeof jobOps>>>
|
||||
export type JobOpsQueryError = void
|
||||
|
||||
|
||||
export function useJobOps<TData = Awaited<ReturnType<typeof jobOps>>, TError = void>(
|
||||
options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>> & Pick<
|
||||
export function useJobOps<
|
||||
TData = Awaited<ReturnType<typeof jobOps>>,
|
||||
TError = void,
|
||||
>(
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof jobOps>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof jobOps>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useJobOps<TData = Awaited<ReturnType<typeof jobOps>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useJobOps<
|
||||
TData = Awaited<ReturnType<typeof jobOps>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof jobOps>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof jobOps>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useJobOps<TData = Awaited<ReturnType<typeof jobOps>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useJobOps<
|
||||
TData = Awaited<ReturnType<typeof jobOps>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 큐 운영 스냅샷
|
||||
*/
|
||||
|
||||
export function useJobOps<TData = Awaited<ReturnType<typeof jobOps>>, TError = void>(
|
||||
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useJobOps<
|
||||
TData = Awaited<ReturnType<typeof jobOps>>,
|
||||
TError = void,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof jobOps>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getJobOpsQueryOptions(options);
|
||||
|
||||
const queryOptions = getJobOpsQueryOptions(options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @summary 잡 상태 조회(폴링)
|
||||
*/
|
||||
export const getJob = (
|
||||
jobId: string,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
jobId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResJob>(
|
||||
{url: `/v1/job/${jobId}`, method: 'GET', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResJob>(
|
||||
{ url: `/v1/job/${jobId}`, method: "GET", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getGetJobQueryKey = (jobId?: string) => {
|
||||
return [`/v1/job/${jobId}`] as const;
|
||||
};
|
||||
|
||||
|
||||
export const getGetJobQueryKey = (jobId?: string,) => {
|
||||
return [
|
||||
`/v1/job/${jobId}`
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getGetJobQueryOptions = <TData = Awaited<ReturnType<typeof getJob>>, TError = void | HTTPValidationError>(jobId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
export const getGetJobQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getJob>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
jobId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
const queryKey = queryOptions?.queryKey ?? getGetJobQueryKey(jobId);
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetJobQueryKey(jobId);
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getJob>>> = ({
|
||||
signal,
|
||||
}) => getJob(jobId, requestOptions, signal);
|
||||
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!jobId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getJob>>> = ({ signal }) => getJob(jobId, requestOptions, signal);
|
||||
export type GetJobQueryResult = NonNullable<Awaited<ReturnType<typeof getJob>>>;
|
||||
export type GetJobQueryError = void | HTTPValidationError;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(jobId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type GetJobQueryResult = NonNullable<Awaited<ReturnType<typeof getJob>>>
|
||||
export type GetJobQueryError = void | HTTPValidationError
|
||||
|
||||
|
||||
export function useGetJob<TData = Awaited<ReturnType<typeof getJob>>, TError = void | HTTPValidationError>(
|
||||
jobId: string, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>> & Pick<
|
||||
export function useGetJob<
|
||||
TData = Awaited<ReturnType<typeof getJob>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
jobId: string,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getJob>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getJob>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useGetJob<TData = Awaited<ReturnType<typeof getJob>>, TError = void | HTTPValidationError>(
|
||||
jobId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetJob<
|
||||
TData = Awaited<ReturnType<typeof getJob>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
jobId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getJob>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getJob>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useGetJob<TData = Awaited<ReturnType<typeof getJob>>, TError = void | HTTPValidationError>(
|
||||
jobId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetJob<
|
||||
TData = Awaited<ReturnType<typeof getJob>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
jobId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 잡 상태 조회(폴링)
|
||||
*/
|
||||
|
||||
export function useGetJob<TData = Awaited<ReturnType<typeof getJob>>, TError = void | HTTPValidationError>(
|
||||
jobId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useGetJob<
|
||||
TData = Awaited<ReturnType<typeof getJob>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
jobId: string,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof getJob>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getGetJobQueryOptions(jobId, options);
|
||||
|
||||
const queryOptions = getGetJobQueryOptions(jobId,options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* max_attempts 를 소진해 dead-letter 로 간 잡을 다시 대기로 돌린다. 내부 운영 전용.
|
||||
* @summary DEAD 잡 재큐
|
||||
*/
|
||||
export const requeueJob = (
|
||||
jobId: string,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
jobId: string,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResJob>(
|
||||
{url: `/v1/job/${jobId}/requeue`, method: 'POST', signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResJob>(
|
||||
{ url: `/v1/job/${jobId}/requeue`, method: "POST", signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
export const getRequeueJobMutationOptions = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
TError,
|
||||
{ jobId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
TError,
|
||||
{ jobId: string },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["requeueJob"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
export const getRequeueJobMutationOptions = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof requeueJob>>, TError,{jobId: string}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
): UseMutationOptions<Awaited<ReturnType<typeof requeueJob>>, TError,{jobId: string}, TContext> => {
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
{ jobId: string }
|
||||
> = (props) => {
|
||||
const { jobId } = props ?? {};
|
||||
|
||||
const mutationKey = ['requeueJob'];
|
||||
const {mutation: mutationOptions, request: requestOptions} = options ?
|
||||
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
||||
options
|
||||
: {...options, mutation: {...options.mutation, mutationKey}}
|
||||
: {mutation: { mutationKey, }, request: undefined};
|
||||
return requeueJob(jobId, requestOptions);
|
||||
};
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type RequeueJobMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof requeueJob>>
|
||||
>;
|
||||
|
||||
const mutationFn: MutationFunction<Awaited<ReturnType<typeof requeueJob>>, {jobId: string}> = (props) => {
|
||||
const {jobId} = props ?? {};
|
||||
export type RequeueJobMutationError = void | HTTPValidationError;
|
||||
|
||||
return requeueJob(jobId,requestOptions)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return { mutationFn, ...mutationOptions }}
|
||||
|
||||
export type RequeueJobMutationResult = NonNullable<Awaited<ReturnType<typeof requeueJob>>>
|
||||
|
||||
export type RequeueJobMutationError = void | HTTPValidationError
|
||||
|
||||
/**
|
||||
/**
|
||||
* @summary DEAD 잡 재큐
|
||||
*/
|
||||
export const useRequeueJob = <TError = void | HTTPValidationError,
|
||||
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof requeueJob>>, TError,{jobId: string}, TContext>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient): UseMutationResult<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
TError,
|
||||
{jobId: string},
|
||||
TContext
|
||||
> => {
|
||||
export const useRequeueJob = <
|
||||
TError = void | HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
TError,
|
||||
{ jobId: string },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof requeueJob>>,
|
||||
TError,
|
||||
{ jobId: string },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getRequeueJobMutationOptions(options);
|
||||
|
||||
const mutationOptions = getRequeueJobMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
}
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import {
|
||||
useQuery
|
||||
} from '@tanstack/react-query';
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type {
|
||||
DataTag,
|
||||
DefinedInitialDataOptions,
|
||||
@ -16,120 +14,174 @@ import type {
|
||||
QueryKey,
|
||||
UndefinedInitialDataOptions,
|
||||
UseQueryOptions,
|
||||
UseQueryResult
|
||||
} from '@tanstack/react-query';
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type {
|
||||
HTTPValidationError,
|
||||
ListMediaParams,
|
||||
ResMediaList
|
||||
} from '.././model';
|
||||
|
||||
import { customFetch } from '../../mutator/custom-fetch';
|
||||
ResMediaList,
|
||||
} from ".././model";
|
||||
|
||||
import { customFetch } from "../../mutator/custom-fetch";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* publishable_only=true 면 ★ 사이트에 실제로 나갈 수 있는 것(승인 + alt 있음)만 — 빌드 스냅샷과 같은 기준이다. unit_id 를 주면 그 객실·메뉴 사진만. ★ 응답에는 항상 source_type(owner/crawl)과 origin_url 이 실린다 — 크롤링 이미지 재게시 권리가 미결이라(docs/DECISIONS.md 1-2) 결론에 따라 CRAWL 을 통째로 걸러내야 하고, 화면이 출처를 모르면 그 판단을 못 한다.
|
||||
* @summary 사진 목록
|
||||
*/
|
||||
export const listMedia = (
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: SecondParameter<typeof customFetch>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
|
||||
|
||||
return customFetch<ResMediaList>(
|
||||
{url: `/v1/place/${placeId}/media/list`, method: 'GET',
|
||||
params, signal
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
return customFetch<ResMediaList>(
|
||||
{ url: `/v1/place/${placeId}/media/list`, method: "GET", params, signal },
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getListMediaQueryKey = (placeId?: string,
|
||||
params?: ListMediaParams,) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/media/list`, ...(params ? [params]: [])
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getListMediaQueryOptions = <TData = Awaited<ReturnType<typeof listMedia>>, TError = void | HTTPValidationError>(placeId: string,
|
||||
params?: ListMediaParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
export const getListMediaQueryKey = (
|
||||
placeId?: string,
|
||||
params?: ListMediaParams,
|
||||
) => {
|
||||
return [
|
||||
`/v1/place/${placeId}/media/list`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
export const getListMediaQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listMedia>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListMediaQueryKey(placeId,params);
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListMediaQueryKey(placeId, params);
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listMedia>>> = ({
|
||||
signal,
|
||||
}) => listMedia(placeId, params, requestOptions, signal);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listMedia>>> = ({ signal }) => listMedia(placeId,params, requestOptions, signal);
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!placeId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export type ListMediaQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listMedia>>
|
||||
>;
|
||||
export type ListMediaQueryError = void | HTTPValidationError;
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(placeId), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
}
|
||||
|
||||
export type ListMediaQueryResult = NonNullable<Awaited<ReturnType<typeof listMedia>>>
|
||||
export type ListMediaQueryError = void | HTTPValidationError
|
||||
|
||||
|
||||
export function useListMedia<TData = Awaited<ReturnType<typeof listMedia>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params: undefined | ListMediaParams, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>> & Pick<
|
||||
export function useListMedia<
|
||||
TData = Awaited<ReturnType<typeof listMedia>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params: undefined | ListMediaParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listMedia>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listMedia>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListMedia<TData = Awaited<ReturnType<typeof listMedia>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>> & Pick<
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListMedia<
|
||||
TData = Awaited<ReturnType<typeof listMedia>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof listMedia>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof listMedia>>
|
||||
> , 'initialData'
|
||||
>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
export function useListMedia<TData = Awaited<ReturnType<typeof listMedia>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useListMedia<
|
||||
TData = Awaited<ReturnType<typeof listMedia>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary 사진 목록
|
||||
*/
|
||||
|
||||
export function useListMedia<TData = Awaited<ReturnType<typeof listMedia>>, TError = void | HTTPValidationError>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
||||
, queryClient?: QueryClient
|
||||
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
||||
export function useListMedia<
|
||||
TData = Awaited<ReturnType<typeof listMedia>>,
|
||||
TError = void | HTTPValidationError,
|
||||
>(
|
||||
placeId: string,
|
||||
params?: ListMediaParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<Awaited<ReturnType<typeof listMedia>>, TError, TData>
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getListMediaQueryOptions(placeId, params, options);
|
||||
|
||||
const queryOptions = getListMediaQueryOptions(placeId,params,options)
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey ;
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { AuditCheckDataRecommendation } from './auditCheckDataRecommendation';
|
||||
import type { AuditCheckDataRecommendation } from "./auditCheckDataRecommendation";
|
||||
|
||||
export interface AuditCheckData {
|
||||
id: string;
|
||||
|
||||
@ -12,8 +12,7 @@
|
||||
이으려면 "먼저 가입한 쪽의 소유"를 증명받아야 하는데, 그 증명 없이 이메일만 보고 이으면
|
||||
남이 먼저 만들어 둔 계정에 내 구글 로그인이 들어간다(계정 선점). 보류 사유는 DECISIONS.md 1절.
|
||||
*/
|
||||
export type AuthProvider = typeof AuthProvider[keyof typeof AuthProvider];
|
||||
|
||||
export type AuthProvider = (typeof AuthProvider)[keyof typeof AuthProvider];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const AuthProvider = {
|
||||
|
||||
@ -8,8 +8,7 @@
|
||||
/**
|
||||
* site_versions.build_status 코드값. 정적 빌드는 개별 재빌드 단위로 돈다.
|
||||
*/
|
||||
export type BuildStatus = typeof BuildStatus[keyof typeof BuildStatus];
|
||||
|
||||
export type BuildStatus = (typeof BuildStatus)[keyof typeof BuildStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const BuildStatus = {
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
export type CheckSlugParams = {
|
||||
/**
|
||||
* 쓰고 싶은 주소(예: doflo)
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* 쓰고 싶은 주소(예: doflo)
|
||||
*/
|
||||
slug: string;
|
||||
};
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ErrorInfoSuccess } from './errorInfoSuccess';
|
||||
import type { ErrorInfoCode } from './errorInfoCode';
|
||||
import type { ErrorInfoDesc } from './errorInfoDesc';
|
||||
import type { ErrorInfoSuccess } from "./errorInfoSuccess";
|
||||
import type { ErrorInfoCode } from "./errorInfoCode";
|
||||
import type { ErrorInfoDesc } from "./errorInfoDesc";
|
||||
|
||||
/**
|
||||
* 모든 응답에 공통으로 실리는 결과 정보. result.success / code / desc 로 내려간다.
|
||||
|
||||
@ -11,8 +11,8 @@
|
||||
카카오는 안정적인 고유 place id 를 준다 → 그걸로 중복 등록을 막는다.
|
||||
네이버는 고유 id 가 없다(응답의 link 는 업체 홈페이지다) → 상호명+도로명주소로 막는다.
|
||||
*/
|
||||
export type ExternalPlaceSource = typeof ExternalPlaceSource[keyof typeof ExternalPlaceSource];
|
||||
|
||||
export type ExternalPlaceSource =
|
||||
(typeof ExternalPlaceSource)[keyof typeof ExternalPlaceSource];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const ExternalPlaceSource = {
|
||||
|
||||
@ -4,17 +4,17 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { FactDataUnitId } from './factDataUnitId';
|
||||
import type { FactDataValue } from './factDataValue';
|
||||
import type { FactDataSummary } from './factDataSummary';
|
||||
import type { FactDataUnit } from './factDataUnit';
|
||||
import type { SourceType } from './sourceType';
|
||||
import type { FactDataSourceUrl } from './factDataSourceUrl';
|
||||
import type { FactDataCollectedAt } from './factDataCollectedAt';
|
||||
import type { FactDataVerifiedBy } from './factDataVerifiedBy';
|
||||
import type { FactDataVerifiedAt } from './factDataVerifiedAt';
|
||||
import type { FactStatus } from './factStatus';
|
||||
import type { FactDataExpiresAt } from './factDataExpiresAt';
|
||||
import type { FactDataUnitId } from "./factDataUnitId";
|
||||
import type { FactDataValue } from "./factDataValue";
|
||||
import type { FactDataSummary } from "./factDataSummary";
|
||||
import type { FactDataUnit } from "./factDataUnit";
|
||||
import type { SourceType } from "./sourceType";
|
||||
import type { FactDataSourceUrl } from "./factDataSourceUrl";
|
||||
import type { FactDataCollectedAt } from "./factDataCollectedAt";
|
||||
import type { FactDataVerifiedBy } from "./factDataVerifiedBy";
|
||||
import type { FactDataVerifiedAt } from "./factDataVerifiedAt";
|
||||
import type { FactStatus } from "./factStatus";
|
||||
import type { FactDataExpiresAt } from "./factDataExpiresAt";
|
||||
|
||||
export interface FactData {
|
||||
fact_id: string;
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
* facts.status / faqs.status / routes.status 공용 검증 상태.
|
||||
★ VERIFIED 와 CORRECTED 만 사이트에 노출한다(PUBLISHABLE_FACT_STATUSES).
|
||||
*/
|
||||
export type FactStatus = typeof FactStatus[keyof typeof FactStatus];
|
||||
|
||||
export type FactStatus = (typeof FactStatus)[keyof typeof FactStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const FactStatus = {
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
* fact 기록 결과. 재수집(업데이트)이 무엇을 했는지 호출측이 알아야 한다 —
|
||||
특히 사이트 재빌드가 필요한 경우(PUBLISHED_REPLACED)를 구분해야 한다.
|
||||
*/
|
||||
export type FactWriteOutcome = typeof FactWriteOutcome[keyof typeof FactWriteOutcome];
|
||||
|
||||
export type FactWriteOutcome =
|
||||
(typeof FactWriteOutcome)[keyof typeof FactWriteOutcome];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const FactWriteOutcome = {
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { FaqDataSourceFactIds } from './faqDataSourceFactIds';
|
||||
import type { SourceType } from './sourceType';
|
||||
import type { FactStatus } from './factStatus';
|
||||
import type { FaqDataCreatedAt } from './faqDataCreatedAt';
|
||||
import type { FaqDataUpdatedAt } from './faqDataUpdatedAt';
|
||||
import type { FaqDataSourceFactIds } from "./faqDataSourceFactIds";
|
||||
import type { SourceType } from "./sourceType";
|
||||
import type { FactStatus } from "./factStatus";
|
||||
import type { FaqDataCreatedAt } from "./faqDataCreatedAt";
|
||||
import type { FaqDataUpdatedAt } from "./faqDataUpdatedAt";
|
||||
|
||||
export interface FaqData {
|
||||
faq_id: string;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { FieldSpecDataUnit } from './fieldSpecDataUnit';
|
||||
import type { FieldSpecDataUnit } from "./fieldSpecDataUnit";
|
||||
|
||||
/**
|
||||
* 업종 스키마의 필드 정의 — 관리 화면이 입력 폼을 그리는 데 쓴다.
|
||||
|
||||
@ -5,4 +5,6 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqSyncFestivalsAreaCode = string | null;
|
||||
export type GetGuideParams = {
|
||||
place_id: string;
|
||||
};
|
||||
@ -6,19 +6,19 @@
|
||||
*/
|
||||
|
||||
export type GetWeatherParams = {
|
||||
/**
|
||||
* @minLength 1
|
||||
* @maxLength 10
|
||||
*/
|
||||
region_code: string;
|
||||
/**
|
||||
* @minimum -90
|
||||
* @maximum 90
|
||||
*/
|
||||
latitude: number;
|
||||
/**
|
||||
* @minimum -180
|
||||
* @maximum 180
|
||||
*/
|
||||
longitude: number;
|
||||
/**
|
||||
* @minLength 1
|
||||
* @maxLength 10
|
||||
*/
|
||||
region_code: string;
|
||||
/**
|
||||
* @minimum -90
|
||||
* @maximum 90
|
||||
*/
|
||||
latitude: number;
|
||||
/**
|
||||
* @minimum -180
|
||||
* @maximum 180
|
||||
*/
|
||||
longitude: number;
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ValidationError } from './validationError';
|
||||
import type { ValidationError } from "./validationError";
|
||||
|
||||
export interface HTTPValidationError {
|
||||
detail?: ValidationError[];
|
||||
|
||||
@ -5,318 +5,333 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export * from './auditCheckData';
|
||||
export * from './auditCheckDataRecommendation';
|
||||
export * from './authProvider';
|
||||
export * from './buildStatus';
|
||||
export * from './checkSlugParams';
|
||||
export * from './errorInfo';
|
||||
export * from './errorInfoCode';
|
||||
export * from './errorInfoDesc';
|
||||
export * from './errorInfoSuccess';
|
||||
export * from './externalPlaceSource';
|
||||
export * from './factData';
|
||||
export * from './factDataCollectedAt';
|
||||
export * from './factDataExpiresAt';
|
||||
export * from './factDataSourceUrl';
|
||||
export * from './factDataSummary';
|
||||
export * from './factDataUnit';
|
||||
export * from './factDataUnitId';
|
||||
export * from './factDataValue';
|
||||
export * from './factDataVerifiedAt';
|
||||
export * from './factDataVerifiedBy';
|
||||
export * from './factStatus';
|
||||
export * from './factWriteOutcome';
|
||||
export * from './faqData';
|
||||
export * from './faqDataCreatedAt';
|
||||
export * from './faqDataSourceFactIds';
|
||||
export * from './faqDataUpdatedAt';
|
||||
export * from './fieldSpecData';
|
||||
export * from './fieldSpecDataUnit';
|
||||
export * from './getWeatherParams';
|
||||
export * from './hTTPValidationError';
|
||||
export * from './jobData';
|
||||
export * from './jobDataCreatedAt';
|
||||
export * from './jobDataLastError';
|
||||
export * from './jobDataResult';
|
||||
export * from './jobDataRunAfter';
|
||||
export * from './jobDataRunStartedAt';
|
||||
export * from './jobDataUpdatedAt';
|
||||
export * from './jobStatus';
|
||||
export * from './jobType';
|
||||
export * from './linkChannel';
|
||||
export * from './linkData';
|
||||
export * from './linkDataConfirmedAt';
|
||||
export * from './linkDataDiscoveredAt';
|
||||
export * from './linkDataTitle';
|
||||
export * from './listContentsParams';
|
||||
export * from './listFactsParams';
|
||||
export * from './listFaqsParams';
|
||||
export * from './listLinksParams';
|
||||
export * from './listMediaParams';
|
||||
export * from './listMySitesParams';
|
||||
export * from './listPlacesParams';
|
||||
export * from './listShowcaseParams';
|
||||
export * from './localContentData';
|
||||
export * from './localContentDataBody';
|
||||
export * from './localContentDataCollectedAt';
|
||||
export * from './localContentDataDisplayEndAt';
|
||||
export * from './localContentDataDisplayStartAt';
|
||||
export * from './localContentDataExternalId';
|
||||
export * from './localContentDataPublishedAt';
|
||||
export * from './localContentDataTitle';
|
||||
export * from './localContentStatus';
|
||||
export * from './localContentType';
|
||||
export * from './localSource';
|
||||
export * from './mediaData';
|
||||
export * from './mediaDataAltText';
|
||||
export * from './mediaDataCreatedAt';
|
||||
export * from './mediaDataHeight';
|
||||
export * from './mediaDataLabel';
|
||||
export * from './mediaDataOriginUrl';
|
||||
export * from './mediaDataSourceUrl';
|
||||
export * from './mediaDataUnitId';
|
||||
export * from './mediaDataVisionConfidence';
|
||||
export * from './mediaDataWidth';
|
||||
export * from './mediaStatus';
|
||||
export * from './mySiteData';
|
||||
export * from './mySiteDataCreatedAt';
|
||||
export * from './mySiteDataDomain';
|
||||
export * from './mySiteDataPublishedAt';
|
||||
export * from './mySiteDataThumbnailUrl';
|
||||
export * from './mySiteDataRoadAddress';
|
||||
export * from './mySiteDataSiteId';
|
||||
export * from './mySiteDataStatus';
|
||||
export * from './mySiteDataTemplateId';
|
||||
export * from './placeCandidate';
|
||||
export * from './placeCandidateAddress';
|
||||
export * from './placeCandidateCategoryName';
|
||||
export * from './placeCandidateExternalPlaceId';
|
||||
export * from './placeCandidateLatitude';
|
||||
export * from './placeCandidateLongitude';
|
||||
export * from './placeCandidateNaverPlaceUrl';
|
||||
export * from './placeCandidatePhone';
|
||||
export * from './placeCandidatePlaceUrl';
|
||||
export * from './placeCandidateRoadAddress';
|
||||
export * from './placeCategory';
|
||||
export * from './placeData';
|
||||
export * from './placeDataAddress';
|
||||
export * from './placeDataContentUpdatedAt';
|
||||
export * from './placeDataCreatedAt';
|
||||
export * from './placeDataExternalPlaceId';
|
||||
export * from './placeDataExternalSource';
|
||||
export * from './placeDataLatitude';
|
||||
export * from './placeDataLongitude';
|
||||
export * from './placeDataOwnerUserId';
|
||||
export * from './placeDataPhone';
|
||||
export * from './placeDataRegionCode';
|
||||
export * from './placeDataRoadAddress';
|
||||
export * from './placeDataVerifiedAt';
|
||||
export * from './placeSearchItem';
|
||||
export * from './placeSearchItemCategory';
|
||||
export * from './placeSearchItemCategoryName';
|
||||
export * from './placeSearchItemRoadAddress';
|
||||
export * from './placeStatus';
|
||||
export * from './publishAction';
|
||||
export * from './publishLogData';
|
||||
export * from './publishLogDataCreatedAt';
|
||||
export * from './publishLogDataDetail';
|
||||
export * from './publishLogDataRejectReason';
|
||||
export * from './publishRejectReason';
|
||||
export * from './publishResult';
|
||||
export * from './renderStatusData';
|
||||
export * from './renderStatusDataError';
|
||||
export * from './renderStatusDataRenderedAt';
|
||||
export * from './renderStatusDataRenderedVersion';
|
||||
export * from './reqCreateFaq';
|
||||
export * from './reqCreateFaqSortOrder';
|
||||
export * from './reqCreateLink';
|
||||
export * from './reqCreateLinkTitle';
|
||||
export * from './reqCreatePlace';
|
||||
export * from './reqCreatePlaceOwnerUserId';
|
||||
export * from './reqCreateUnit';
|
||||
export * from './reqExtractFacts';
|
||||
export * from './reqGoogleLogin';
|
||||
export * from './reqLogin';
|
||||
export * from './reqPublishLocalContent';
|
||||
export * from './reqSignup';
|
||||
export * from './reqSignupName';
|
||||
export * from './reqSiteSlug';
|
||||
export * from './reqSiteStatus';
|
||||
export * from './reqSiteTemplate';
|
||||
export * from './reqSiteTheme';
|
||||
export * from './reqSiteThemeTheme';
|
||||
export * from './reqStartBuild';
|
||||
export * from './reqStartCollect';
|
||||
export * from './reqStartCopy';
|
||||
export * from './reqStartVision';
|
||||
export * from './reqSyncFestivals';
|
||||
export * from './reqSyncFestivalsAreaCode';
|
||||
export * from './reqSyncFestivalsStartDate';
|
||||
export * from './reqTransitionFact';
|
||||
export * from './reqTransitionFactValue';
|
||||
export * from './reqTransitionFaq';
|
||||
export * from './reqTransitionFaqAnswer';
|
||||
export * from './reqTransitionFaqQuestion';
|
||||
export * from './reqUpdateLocalContent';
|
||||
export * from './reqUpdateLocalContentBody';
|
||||
export * from './reqUpdateLocalContentBodyAnyOf';
|
||||
export * from './reqUpdateLocalContentDisplayEndAt';
|
||||
export * from './reqUpdateLocalContentDisplayStartAt';
|
||||
export * from './reqUpdateLocalContentTitle';
|
||||
export * from './reqUpdateMe';
|
||||
export * from './reqUpdateMeContactNumber';
|
||||
export * from './reqUpdateMeEmail';
|
||||
export * from './reqUpdateMeName';
|
||||
export * from './reqUpdateMePassword';
|
||||
export * from './reqUpdatePlace';
|
||||
export * from './reqUpdatePlaceName';
|
||||
export * from './reqUpdatePlaceOwnerUserId';
|
||||
export * from './reqUpdatePlaceStatus';
|
||||
export * from './reqUpsertFact';
|
||||
export * from './reqUpsertFactExpiresAt';
|
||||
export * from './reqUpsertFactSourceUrl';
|
||||
export * from './reqUpsertFactUnitId';
|
||||
export * from './reqUpsertFactValue';
|
||||
export * from './reqVerifyPlace';
|
||||
export * from './reqVerifyPlaceAddress';
|
||||
export * from './reqVerifyPlaceByUrl';
|
||||
export * from './reqVerifyPlaceLatitude';
|
||||
export * from './reqVerifyPlaceLongitude';
|
||||
export * from './reqVerifyPlacePhone';
|
||||
export * from './reqVerifyPlacePlaceUrl';
|
||||
export * from './reqVerifyPlaceRegionCode';
|
||||
export * from './reqVerifyPlaceRoadAddress';
|
||||
export * from './resCategorySchema';
|
||||
export * from './resCategorySchemaCategory';
|
||||
export * from './resCategorySchemaLabel';
|
||||
export * from './resCategorySchemaMsg';
|
||||
export * from './resExtractFacts';
|
||||
export * from './resExtractFactsMsg';
|
||||
export * from './resExtractedFact';
|
||||
export * from './resExtractedFactReason';
|
||||
export * from './resExtractedFactUnitName';
|
||||
export * from './resFact';
|
||||
export * from './resFactFact';
|
||||
export * from './resFactList';
|
||||
export * from './resFactListMsg';
|
||||
export * from './resFactMsg';
|
||||
export * from './resFactOutcome';
|
||||
export * from './resFaq';
|
||||
export * from './resFaqFaq';
|
||||
export * from './resFaqList';
|
||||
export * from './resFaqListMsg';
|
||||
export * from './resFaqMsg';
|
||||
export * from './resJob';
|
||||
export * from './resJobJob';
|
||||
export * from './resJobMsg';
|
||||
export * from './resJobOps';
|
||||
export * from './resJobOpsMsg';
|
||||
export * from './resLink';
|
||||
export * from './resLinkLink';
|
||||
export * from './resLinkList';
|
||||
export * from './resLinkListMsg';
|
||||
export * from './resLinkMsg';
|
||||
export * from './resLocalContentList';
|
||||
export * from './resLocalContentListMsg';
|
||||
export * from './resLogin';
|
||||
export * from './resLoginMsg';
|
||||
export * from './resMe';
|
||||
export * from './resMeContactNumber';
|
||||
export * from './resMeEmail';
|
||||
export * from './resMeMsg';
|
||||
export * from './resMeName';
|
||||
export * from './resMediaList';
|
||||
export * from './resMediaListMsg';
|
||||
export * from './resMySites';
|
||||
export * from './resMySitesMsg';
|
||||
export * from './resPlace';
|
||||
export * from './resPlaceList';
|
||||
export * from './resPlaceListMsg';
|
||||
export * from './resPlaceMsg';
|
||||
export * from './resPlacePlace';
|
||||
export * from './resPlaceSearch';
|
||||
export * from './resPlaceSearchMsg';
|
||||
export * from './resPlaceSearchSource';
|
||||
export * from './resPublishLogs';
|
||||
export * from './resPublishLogsMsg';
|
||||
export * from './resRefreshToken';
|
||||
export * from './resRefreshTokenMsg';
|
||||
export * from './resSeoAudit';
|
||||
export * from './resSeoAuditMsg';
|
||||
export * from './resSeoAuditSummary';
|
||||
export * from './resSeoAuditVisibility';
|
||||
export * from './resShowcase';
|
||||
export * from './resShowcaseMsg';
|
||||
export * from './resSite';
|
||||
export * from './resSiteCurrentVersion';
|
||||
export * from './resSiteMsg';
|
||||
export * from './resSiteSite';
|
||||
export * from './resSiteSlug';
|
||||
export * from './resSiteSlugMsg';
|
||||
export * from './resSiteSlugReason';
|
||||
export * from './resSiteSlugSite';
|
||||
export * from './resSiteSlugSuggestion';
|
||||
export * from './resSiteVersions';
|
||||
export * from './resSiteVersionsMsg';
|
||||
export * from './resSlugCheck';
|
||||
export * from './resSlugCheckMsg';
|
||||
export * from './resSlugCheckReason';
|
||||
export * from './resSlugCheckSuggestion';
|
||||
export * from './resStartBuild';
|
||||
export * from './resStartBuildJobId';
|
||||
export * from './resStartBuildMsg';
|
||||
export * from './resStartBuildStatus';
|
||||
export * from './resStartCollect';
|
||||
export * from './resStartCollectJobId';
|
||||
export * from './resStartCollectMsg';
|
||||
export * from './resStartCollectStatus';
|
||||
export * from './resStartCopy';
|
||||
export * from './resStartCopyJobId';
|
||||
export * from './resStartCopyMsg';
|
||||
export * from './resStartCopyStatus';
|
||||
export * from './resStartVision';
|
||||
export * from './resStartVisionJobId';
|
||||
export * from './resStartVisionMsg';
|
||||
export * from './resStartVisionStatus';
|
||||
export * from './resSyncFestivals';
|
||||
export * from './resSyncFestivalsMsg';
|
||||
export * from './resUnit';
|
||||
export * from './resUnitList';
|
||||
export * from './resUnitListMsg';
|
||||
export * from './resUnitMsg';
|
||||
export * from './resUnitUnit';
|
||||
export * from './resVerifyCandidates';
|
||||
export * from './resVerifyCandidatesMsg';
|
||||
export * from './resVerifyCandidatesReason';
|
||||
export * from './resVerifyCandidatesSource';
|
||||
export * from './resWeather';
|
||||
export * from './resWeatherMsg';
|
||||
export * from './resWeatherWeather';
|
||||
export * from './searchPlacesPublicParams';
|
||||
export * from './showcaseItem';
|
||||
export * from './showcaseItemRegion';
|
||||
export * from './showcaseItemThumbnailUrl';
|
||||
export * from './siteData';
|
||||
export * from './siteDataCurrentVersionId';
|
||||
export * from './siteDataDomain';
|
||||
export * from './siteDataPublishedAt';
|
||||
export * from './siteDataTemplateId';
|
||||
export * from './siteDataTheme';
|
||||
export * from './siteDataThemeAnyOf';
|
||||
export * from './siteDataThumbnailUrl';
|
||||
export * from './siteStatus';
|
||||
export * from './siteVersionData';
|
||||
export * from './siteVersionDataBuildError';
|
||||
export * from './siteVersionDataBuiltAt';
|
||||
export * from './siteVersionDataCreatedAt';
|
||||
export * from './sourceType';
|
||||
export * from './unitData';
|
||||
export * from './userRole';
|
||||
export * from './validationError';
|
||||
export * from './validationErrorCtx';
|
||||
export * from './validationErrorLocItem';
|
||||
export * from './verifyCandidatesParams';
|
||||
export * from './weatherData';
|
||||
export * from './weatherDataTimezone';
|
||||
export * from './weatherDataWindSpeed';
|
||||
export * from "./auditCheckData";
|
||||
export * from "./auditCheckDataRecommendation";
|
||||
export * from "./authProvider";
|
||||
export * from "./buildStatus";
|
||||
export * from "./checkSlugParams";
|
||||
export * from "./errorInfo";
|
||||
export * from "./errorInfoCode";
|
||||
export * from "./errorInfoDesc";
|
||||
export * from "./errorInfoSuccess";
|
||||
export * from "./externalPlaceSource";
|
||||
export * from "./factData";
|
||||
export * from "./factDataCollectedAt";
|
||||
export * from "./factDataExpiresAt";
|
||||
export * from "./factDataSourceUrl";
|
||||
export * from "./factDataSummary";
|
||||
export * from "./factDataUnit";
|
||||
export * from "./factDataUnitId";
|
||||
export * from "./factDataValue";
|
||||
export * from "./factDataVerifiedAt";
|
||||
export * from "./factDataVerifiedBy";
|
||||
export * from "./factStatus";
|
||||
export * from "./factWriteOutcome";
|
||||
export * from "./faqData";
|
||||
export * from "./faqDataCreatedAt";
|
||||
export * from "./faqDataSourceFactIds";
|
||||
export * from "./faqDataUpdatedAt";
|
||||
export * from "./fieldSpecData";
|
||||
export * from "./fieldSpecDataUnit";
|
||||
export * from "./getGuideParams";
|
||||
export * from "./getWeatherParams";
|
||||
export * from "./hTTPValidationError";
|
||||
export * from "./jobData";
|
||||
export * from "./jobDataCreatedAt";
|
||||
export * from "./jobDataLastError";
|
||||
export * from "./jobDataResult";
|
||||
export * from "./jobDataRunAfter";
|
||||
export * from "./jobDataRunStartedAt";
|
||||
export * from "./jobDataUpdatedAt";
|
||||
export * from "./jobStatus";
|
||||
export * from "./jobType";
|
||||
export * from "./linkChannel";
|
||||
export * from "./linkData";
|
||||
export * from "./linkDataConfirmedAt";
|
||||
export * from "./linkDataDiscoveredAt";
|
||||
export * from "./linkDataTitle";
|
||||
export * from "./listContentsParams";
|
||||
export * from "./listFactsParams";
|
||||
export * from "./listFaqsParams";
|
||||
export * from "./listLinksParams";
|
||||
export * from "./listMediaParams";
|
||||
export * from "./listMySitesParams";
|
||||
export * from "./listPlacesParams";
|
||||
export * from "./listShowcaseParams";
|
||||
export * from "./localContentData";
|
||||
export * from "./localContentDataBody";
|
||||
export * from "./localContentDataCollectedAt";
|
||||
export * from "./localContentDataDisplayEndAt";
|
||||
export * from "./localContentDataDisplayStartAt";
|
||||
export * from "./localContentDataExternalId";
|
||||
export * from "./localContentDataPublishedAt";
|
||||
export * from "./localContentDataTitle";
|
||||
export * from "./localContentStatus";
|
||||
export * from "./localContentType";
|
||||
export * from "./localSource";
|
||||
export * from "./mediaData";
|
||||
export * from "./mediaDataAltText";
|
||||
export * from "./mediaDataCreatedAt";
|
||||
export * from "./mediaDataHeight";
|
||||
export * from "./mediaDataLabel";
|
||||
export * from "./mediaDataOriginUrl";
|
||||
export * from "./mediaDataSourceUrl";
|
||||
export * from "./mediaDataUnitId";
|
||||
export * from "./mediaDataVisionConfidence";
|
||||
export * from "./mediaDataWidth";
|
||||
export * from "./mediaStatus";
|
||||
export * from "./mySiteData";
|
||||
export * from "./mySiteDataCreatedAt";
|
||||
export * from "./mySiteDataDomain";
|
||||
export * from "./mySiteDataPublishedAt";
|
||||
export * from "./mySiteDataRoadAddress";
|
||||
export * from "./mySiteDataSiteId";
|
||||
export * from "./mySiteDataStatus";
|
||||
export * from "./mySiteDataTemplateId";
|
||||
export * from "./mySiteDataThumbnailUrl";
|
||||
export * from "./placeCandidate";
|
||||
export * from "./placeCandidateAddress";
|
||||
export * from "./placeCandidateCategoryName";
|
||||
export * from "./placeCandidateExternalPlaceId";
|
||||
export * from "./placeCandidateLatitude";
|
||||
export * from "./placeCandidateLongitude";
|
||||
export * from "./placeCandidateNaverPlaceUrl";
|
||||
export * from "./placeCandidatePhone";
|
||||
export * from "./placeCandidatePlaceUrl";
|
||||
export * from "./placeCandidateRoadAddress";
|
||||
export * from "./placeCategory";
|
||||
export * from "./placeContentData";
|
||||
export * from "./placeContentDataBody";
|
||||
export * from "./placeContentDataCollectedAt";
|
||||
export * from "./placeContentDataDisplayEndAt";
|
||||
export * from "./placeData";
|
||||
export * from "./placeDataAddress";
|
||||
export * from "./placeDataContentUpdatedAt";
|
||||
export * from "./placeDataCreatedAt";
|
||||
export * from "./placeDataExternalPlaceId";
|
||||
export * from "./placeDataExternalSource";
|
||||
export * from "./placeDataLatitude";
|
||||
export * from "./placeDataLongitude";
|
||||
export * from "./placeDataOwnerUserId";
|
||||
export * from "./placeDataPhone";
|
||||
export * from "./placeDataRegionCode";
|
||||
export * from "./placeDataRoadAddress";
|
||||
export * from "./placeDataVerifiedAt";
|
||||
export * from "./placeSearchItem";
|
||||
export * from "./placeSearchItemCategory";
|
||||
export * from "./placeSearchItemCategoryName";
|
||||
export * from "./placeSearchItemNaverPlaceUrl";
|
||||
export * from "./placeSearchItemRoadAddress";
|
||||
export * from "./placeStatus";
|
||||
export * from "./publishAction";
|
||||
export * from "./publishLogData";
|
||||
export * from "./publishLogDataCreatedAt";
|
||||
export * from "./publishLogDataDetail";
|
||||
export * from "./publishLogDataRejectReason";
|
||||
export * from "./publishRejectReason";
|
||||
export * from "./publishResult";
|
||||
export * from "./relayParams";
|
||||
export * from "./renderStatusData";
|
||||
export * from "./renderStatusDataError";
|
||||
export * from "./renderStatusDataRenderedAt";
|
||||
export * from "./renderStatusDataRenderedVersion";
|
||||
export * from "./reqCreateFaq";
|
||||
export * from "./reqCreateFaqSortOrder";
|
||||
export * from "./reqCreateLink";
|
||||
export * from "./reqCreateLinkTitle";
|
||||
export * from "./reqCreatePlace";
|
||||
export * from "./reqCreateUnit";
|
||||
export * from "./reqExtractFacts";
|
||||
export * from "./reqGoogleLogin";
|
||||
export * from "./reqHidePlaceContent";
|
||||
export * from "./reqLogin";
|
||||
export * from "./reqPublishLocalContent";
|
||||
export * from "./reqRollback";
|
||||
export * from "./reqSignup";
|
||||
export * from "./reqSignupName";
|
||||
export * from "./reqSiteSlug";
|
||||
export * from "./reqSiteStatus";
|
||||
export * from "./reqSiteTemplate";
|
||||
export * from "./reqSiteTheme";
|
||||
export * from "./reqSiteThemeTheme";
|
||||
export * from "./reqStartBuild";
|
||||
export * from "./reqStartCollect";
|
||||
export * from "./reqStartCopy";
|
||||
export * from "./reqStartVision";
|
||||
export * from "./reqTransitionFact";
|
||||
export * from "./reqTransitionFactValue";
|
||||
export * from "./reqTransitionFaq";
|
||||
export * from "./reqTransitionFaqAnswer";
|
||||
export * from "./reqTransitionFaqQuestion";
|
||||
export * from "./reqUpdateLocalContent";
|
||||
export * from "./reqUpdateLocalContentBody";
|
||||
export * from "./reqUpdateLocalContentBodyAnyOf";
|
||||
export * from "./reqUpdateLocalContentDisplayEndAt";
|
||||
export * from "./reqUpdateLocalContentDisplayStartAt";
|
||||
export * from "./reqUpdateLocalContentTitle";
|
||||
export * from "./reqUpdateMe";
|
||||
export * from "./reqUpdateMeContactNumber";
|
||||
export * from "./reqUpdateMeEmail";
|
||||
export * from "./reqUpdateMeName";
|
||||
export * from "./reqUpdateMePassword";
|
||||
export * from "./reqUpdatePlace";
|
||||
export * from "./reqUpdatePlaceName";
|
||||
export * from "./reqUpdatePlaceStatus";
|
||||
export * from "./reqUpsertFact";
|
||||
export * from "./reqUpsertFactExpiresAt";
|
||||
export * from "./reqUpsertFactSourceUrl";
|
||||
export * from "./reqUpsertFactUnitId";
|
||||
export * from "./reqUpsertFactValue";
|
||||
export * from "./reqVerifyPlace";
|
||||
export * from "./reqVerifyPlaceAddress";
|
||||
export * from "./reqVerifyPlaceByUrl";
|
||||
export * from "./reqVerifyPlaceCategoryName";
|
||||
export * from "./reqVerifyPlaceLatitude";
|
||||
export * from "./reqVerifyPlaceLongitude";
|
||||
export * from "./reqVerifyPlacePhone";
|
||||
export * from "./reqVerifyPlacePlaceUrl";
|
||||
export * from "./reqVerifyPlaceRegionCode";
|
||||
export * from "./reqVerifyPlaceRoadAddress";
|
||||
export * from "./resCategorySchema";
|
||||
export * from "./resCategorySchemaCategory";
|
||||
export * from "./resCategorySchemaLabel";
|
||||
export * from "./resCategorySchemaMsg";
|
||||
export * from "./resExtractFacts";
|
||||
export * from "./resExtractFactsMsg";
|
||||
export * from "./resExtractedFact";
|
||||
export * from "./resExtractedFactReason";
|
||||
export * from "./resExtractedFactUnitName";
|
||||
export * from "./resFact";
|
||||
export * from "./resFactFact";
|
||||
export * from "./resFactList";
|
||||
export * from "./resFactListMsg";
|
||||
export * from "./resFactMsg";
|
||||
export * from "./resFactOutcome";
|
||||
export * from "./resFaq";
|
||||
export * from "./resFaqFaq";
|
||||
export * from "./resFaqList";
|
||||
export * from "./resFaqListMsg";
|
||||
export * from "./resFaqMsg";
|
||||
export * from "./resJob";
|
||||
export * from "./resJobJob";
|
||||
export * from "./resJobMsg";
|
||||
export * from "./resJobOps";
|
||||
export * from "./resJobOpsMsg";
|
||||
export * from "./resLink";
|
||||
export * from "./resLinkLink";
|
||||
export * from "./resLinkList";
|
||||
export * from "./resLinkListMsg";
|
||||
export * from "./resLinkMsg";
|
||||
export * from "./resLocalContentList";
|
||||
export * from "./resLocalContentListMsg";
|
||||
export * from "./resLocalGuide";
|
||||
export * from "./resLocalGuideAttractionsItem";
|
||||
export * from "./resLocalGuideCoursesItem";
|
||||
export * from "./resLocalGuideFestivalsItem";
|
||||
export * from "./resLocalGuideItinerariesItem";
|
||||
export * from "./resLocalGuideMsg";
|
||||
export * from "./resLocalGuideRestaurantsItem";
|
||||
export * from "./resLocalGuideSyncedAt";
|
||||
export * from "./resLogin";
|
||||
export * from "./resLoginMsg";
|
||||
export * from "./resMe";
|
||||
export * from "./resMeContactNumber";
|
||||
export * from "./resMeEmail";
|
||||
export * from "./resMeMsg";
|
||||
export * from "./resMeName";
|
||||
export * from "./resMediaList";
|
||||
export * from "./resMediaListMsg";
|
||||
export * from "./resMySites";
|
||||
export * from "./resMySitesMsg";
|
||||
export * from "./resPlace";
|
||||
export * from "./resPlaceContentList";
|
||||
export * from "./resPlaceContentListMsg";
|
||||
export * from "./resPlaceList";
|
||||
export * from "./resPlaceListMsg";
|
||||
export * from "./resPlaceMsg";
|
||||
export * from "./resPlacePlace";
|
||||
export * from "./resPlaceSearch";
|
||||
export * from "./resPlaceSearchMsg";
|
||||
export * from "./resPlaceSearchSource";
|
||||
export * from "./resPublishLogs";
|
||||
export * from "./resPublishLogsMsg";
|
||||
export * from "./resRefreshToken";
|
||||
export * from "./resRefreshTokenMsg";
|
||||
export * from "./resSeoAudit";
|
||||
export * from "./resSeoAuditMsg";
|
||||
export * from "./resSeoAuditSummary";
|
||||
export * from "./resSeoAuditVisibility";
|
||||
export * from "./resShowcase";
|
||||
export * from "./resShowcaseMsg";
|
||||
export * from "./resSite";
|
||||
export * from "./resSiteCurrentVersion";
|
||||
export * from "./resSiteMsg";
|
||||
export * from "./resSiteSite";
|
||||
export * from "./resSiteSlug";
|
||||
export * from "./resSiteSlugMsg";
|
||||
export * from "./resSiteSlugReason";
|
||||
export * from "./resSiteSlugSite";
|
||||
export * from "./resSiteSlugSuggestion";
|
||||
export * from "./resSiteVersions";
|
||||
export * from "./resSiteVersionsMsg";
|
||||
export * from "./resSlugCheck";
|
||||
export * from "./resSlugCheckMsg";
|
||||
export * from "./resSlugCheckReason";
|
||||
export * from "./resSlugCheckSuggestion";
|
||||
export * from "./resStartBuild";
|
||||
export * from "./resStartBuildJobId";
|
||||
export * from "./resStartBuildMsg";
|
||||
export * from "./resStartBuildStatus";
|
||||
export * from "./resStartCollect";
|
||||
export * from "./resStartCollectJobId";
|
||||
export * from "./resStartCollectMsg";
|
||||
export * from "./resStartCollectStatus";
|
||||
export * from "./resStartCopy";
|
||||
export * from "./resStartCopyJobId";
|
||||
export * from "./resStartCopyMsg";
|
||||
export * from "./resStartCopyStatus";
|
||||
export * from "./resStartVision";
|
||||
export * from "./resStartVisionJobId";
|
||||
export * from "./resStartVisionMsg";
|
||||
export * from "./resStartVisionStatus";
|
||||
export * from "./resSyncPlace";
|
||||
export * from "./resSyncPlaceMsg";
|
||||
export * from "./resUnit";
|
||||
export * from "./resUnitList";
|
||||
export * from "./resUnitListMsg";
|
||||
export * from "./resUnitMsg";
|
||||
export * from "./resUnitUnit";
|
||||
export * from "./resVerifyCandidates";
|
||||
export * from "./resVerifyCandidatesMsg";
|
||||
export * from "./resVerifyCandidatesReason";
|
||||
export * from "./resVerifyCandidatesSource";
|
||||
export * from "./resWeather";
|
||||
export * from "./resWeatherMsg";
|
||||
export * from "./resWeatherWeather";
|
||||
export * from "./searchPlacesPublicParams";
|
||||
export * from "./showcaseItem";
|
||||
export * from "./showcaseItemRegion";
|
||||
export * from "./showcaseItemThumbnailUrl";
|
||||
export * from "./siteData";
|
||||
export * from "./siteDataCurrentVersionId";
|
||||
export * from "./siteDataDomain";
|
||||
export * from "./siteDataPublishedAt";
|
||||
export * from "./siteDataTemplateId";
|
||||
export * from "./siteDataTheme";
|
||||
export * from "./siteDataThemeAnyOf";
|
||||
export * from "./siteDataThumbnailUrl";
|
||||
export * from "./siteStatus";
|
||||
export * from "./siteVersionData";
|
||||
export * from "./siteVersionDataBuildError";
|
||||
export * from "./siteVersionDataBuiltAt";
|
||||
export * from "./siteVersionDataCreatedAt";
|
||||
export * from "./sourceType";
|
||||
export * from "./unitData";
|
||||
export * from "./userRole";
|
||||
export * from "./validationError";
|
||||
export * from "./validationErrorCtx";
|
||||
export * from "./validationErrorLocItem";
|
||||
export * from "./verifyCandidatesParams";
|
||||
export * from "./weatherData";
|
||||
export * from "./weatherDataTimezone";
|
||||
export * from "./weatherDataWindSpeed";
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { JobType } from './jobType';
|
||||
import type { JobStatus } from './jobStatus';
|
||||
import type { JobDataResult } from './jobDataResult';
|
||||
import type { JobDataLastError } from './jobDataLastError';
|
||||
import type { JobDataRunAfter } from './jobDataRunAfter';
|
||||
import type { JobDataRunStartedAt } from './jobDataRunStartedAt';
|
||||
import type { JobDataCreatedAt } from './jobDataCreatedAt';
|
||||
import type { JobDataUpdatedAt } from './jobDataUpdatedAt';
|
||||
import type { JobType } from "./jobType";
|
||||
import type { JobStatus } from "./jobStatus";
|
||||
import type { JobDataResult } from "./jobDataResult";
|
||||
import type { JobDataLastError } from "./jobDataLastError";
|
||||
import type { JobDataRunAfter } from "./jobDataRunAfter";
|
||||
import type { JobDataRunStartedAt } from "./jobDataRunStartedAt";
|
||||
import type { JobDataCreatedAt } from "./jobDataCreatedAt";
|
||||
import type { JobDataUpdatedAt } from "./jobDataUpdatedAt";
|
||||
|
||||
export interface JobData {
|
||||
job_id: string;
|
||||
|
||||
@ -11,8 +11,7 @@
|
||||
전이는 전부 조건부 원자 UPDATE(CAS)로만 한다. 실패는 재시도 가능하면
|
||||
PENDING(run_after=백오프)으로 되돌리고, 소진되면 DEAD(dead-letter).
|
||||
*/
|
||||
export type JobStatus = typeof JobStatus[keyof typeof JobStatus];
|
||||
|
||||
export type JobStatus = (typeof JobStatus)[keyof typeof JobStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const JobStatus = {
|
||||
|
||||
@ -11,8 +11,7 @@
|
||||
무거운 잡(브라우저 필요)과 가벼운 잡(HTTP API 만)을 코드로 갈라 둔다 —
|
||||
크롤링 법무 결론이 나면 무거운 잡만 별도 워커 이미지로 분리한다.
|
||||
*/
|
||||
export type JobType = typeof JobType[keyof typeof JobType];
|
||||
|
||||
export type JobType = (typeof JobType)[keyof typeof JobType];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const JobType = {
|
||||
@ -22,4 +21,6 @@ export const JobType = {
|
||||
BUILD: 4,
|
||||
LOCAL_SYNC: 5,
|
||||
AI_CHECK: 6,
|
||||
SONG: 7,
|
||||
ROLLBACK: 8,
|
||||
} as const;
|
||||
|
||||
@ -6,10 +6,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* place_links.channel 코드값. Perplexity 가 발견하는 채널 종류.
|
||||
* place_channels.channel 코드값. Perplexity 가 발견하는 채널 종류.
|
||||
*/
|
||||
export type LinkChannel = typeof LinkChannel[keyof typeof LinkChannel];
|
||||
|
||||
export type LinkChannel = (typeof LinkChannel)[keyof typeof LinkChannel];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const LinkChannel = {
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { LinkChannel } from './linkChannel';
|
||||
import type { LinkDataTitle } from './linkDataTitle';
|
||||
import type { SourceType } from './sourceType';
|
||||
import type { LinkDataDiscoveredAt } from './linkDataDiscoveredAt';
|
||||
import type { LinkDataConfirmedAt } from './linkDataConfirmedAt';
|
||||
import type { LinkChannel } from "./linkChannel";
|
||||
import type { LinkDataTitle } from "./linkDataTitle";
|
||||
import type { SourceType } from "./sourceType";
|
||||
import type { LinkDataDiscoveredAt } from "./linkDataDiscoveredAt";
|
||||
import type { LinkDataConfirmedAt } from "./linkDataConfirmedAt";
|
||||
|
||||
export interface LinkData {
|
||||
link_id: string;
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { LocalContentStatus } from './localContentStatus';
|
||||
import type { LocalContentStatus } from "./localContentStatus";
|
||||
|
||||
export type ListContentsParams = {
|
||||
status?: LocalContentStatus | null;
|
||||
region_code?: string | null;
|
||||
status?: LocalContentStatus | null;
|
||||
region_code?: string | null;
|
||||
};
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
*/
|
||||
|
||||
export type ListFactsParams = {
|
||||
/**
|
||||
* 객실·메뉴·프로그램 단위 fact 만
|
||||
*/
|
||||
unit_id?: string | null;
|
||||
publishable_only?: boolean;
|
||||
/**
|
||||
* 객실·메뉴·프로그램 단위 fact 만
|
||||
*/
|
||||
unit_id?: string | null;
|
||||
publishable_only?: boolean;
|
||||
};
|
||||
|
||||
@ -6,5 +6,5 @@
|
||||
*/
|
||||
|
||||
export type ListFaqsParams = {
|
||||
publishable_only?: boolean;
|
||||
publishable_only?: boolean;
|
||||
};
|
||||
|
||||
@ -6,5 +6,5 @@
|
||||
*/
|
||||
|
||||
export type ListLinksParams = {
|
||||
confirmed_only?: boolean;
|
||||
confirmed_only?: boolean;
|
||||
};
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
*/
|
||||
|
||||
export type ListMediaParams = {
|
||||
/**
|
||||
* 객실·메뉴 단위 사진만
|
||||
*/
|
||||
unit_id?: string | null;
|
||||
publishable_only?: boolean;
|
||||
/**
|
||||
* 객실·메뉴 단위 사진만
|
||||
*/
|
||||
unit_id?: string | null;
|
||||
publishable_only?: boolean;
|
||||
};
|
||||
|
||||
@ -6,13 +6,13 @@
|
||||
*/
|
||||
|
||||
export type ListMySitesParams = {
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
size?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
size?: number;
|
||||
};
|
||||
|
||||
@ -4,29 +4,29 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCategory } from './placeCategory';
|
||||
import type { PlaceStatus } from './placeStatus';
|
||||
import type { PlaceCategory } from "./placeCategory";
|
||||
import type { PlaceStatus } from "./placeStatus";
|
||||
|
||||
export type ListPlacesParams = {
|
||||
/**
|
||||
* 상호명·주소 부분일치
|
||||
*/
|
||||
search?: string | null;
|
||||
/**
|
||||
* 업종
|
||||
*/
|
||||
category?: PlaceCategory | null;
|
||||
/**
|
||||
* 상태
|
||||
*/
|
||||
status?: PlaceStatus | null;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
size?: number;
|
||||
/**
|
||||
* 상호명·주소 부분일치
|
||||
*/
|
||||
search?: string | null;
|
||||
/**
|
||||
* 업종
|
||||
*/
|
||||
category?: PlaceCategory | null;
|
||||
/**
|
||||
* 상태
|
||||
*/
|
||||
status?: PlaceStatus | null;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
size?: number;
|
||||
};
|
||||
|
||||
@ -6,10 +6,10 @@
|
||||
*/
|
||||
|
||||
export type ListShowcaseParams = {
|
||||
/**
|
||||
* 가져올 개수
|
||||
* @minimum 1
|
||||
* @maximum 48
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* 가져올 개수
|
||||
* @minimum 1
|
||||
* @maximum 48
|
||||
*/
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { LocalContentType } from './localContentType';
|
||||
import type { LocalSource } from './localSource';
|
||||
import type { LocalContentDataExternalId } from './localContentDataExternalId';
|
||||
import type { LocalContentDataTitle } from './localContentDataTitle';
|
||||
import type { LocalContentDataBody } from './localContentDataBody';
|
||||
import type { LocalContentStatus } from './localContentStatus';
|
||||
import type { LocalContentDataCollectedAt } from './localContentDataCollectedAt';
|
||||
import type { LocalContentDataPublishedAt } from './localContentDataPublishedAt';
|
||||
import type { LocalContentDataDisplayStartAt } from './localContentDataDisplayStartAt';
|
||||
import type { LocalContentDataDisplayEndAt } from './localContentDataDisplayEndAt';
|
||||
import type { LocalContentType } from "./localContentType";
|
||||
import type { LocalSource } from "./localSource";
|
||||
import type { LocalContentDataExternalId } from "./localContentDataExternalId";
|
||||
import type { LocalContentDataTitle } from "./localContentDataTitle";
|
||||
import type { LocalContentDataBody } from "./localContentDataBody";
|
||||
import type { LocalContentStatus } from "./localContentStatus";
|
||||
import type { LocalContentDataCollectedAt } from "./localContentDataCollectedAt";
|
||||
import type { LocalContentDataPublishedAt } from "./localContentDataPublishedAt";
|
||||
import type { LocalContentDataDisplayStartAt } from "./localContentDataDisplayStartAt";
|
||||
import type { LocalContentDataDisplayEndAt } from "./localContentDataDisplayEndAt";
|
||||
|
||||
export interface LocalContentData {
|
||||
local_content_id: string;
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
/**
|
||||
* 운영 관리자가 검수하는 지역 콘텐츠 발행 상태.
|
||||
*/
|
||||
export type LocalContentStatus = typeof LocalContentStatus[keyof typeof LocalContentStatus];
|
||||
|
||||
export type LocalContentStatus =
|
||||
(typeof LocalContentStatus)[keyof typeof LocalContentStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const LocalContentStatus = {
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
/**
|
||||
* local_contents.content_type 코드값. 행정구역 코드 단위로 캐싱되는 지역 정보 종류.
|
||||
*/
|
||||
export type LocalContentType = typeof LocalContentType[keyof typeof LocalContentType];
|
||||
|
||||
export type LocalContentType =
|
||||
(typeof LocalContentType)[keyof typeof LocalContentType];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const LocalContentType = {
|
||||
@ -17,4 +17,6 @@ export const LocalContentType = {
|
||||
FESTIVAL: 2,
|
||||
ATTRACTION: 3,
|
||||
RESTAURANT: 4,
|
||||
COURSE: 5,
|
||||
STORY: 6,
|
||||
} as const;
|
||||
|
||||
@ -8,8 +8,7 @@
|
||||
/**
|
||||
* local_contents.source 코드값. 어느 외부 API 에서 왔는지.
|
||||
*/
|
||||
export type LocalSource = typeof LocalSource[keyof typeof LocalSource];
|
||||
|
||||
export type LocalSource = (typeof LocalSource)[keyof typeof LocalSource];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const LocalSource = {
|
||||
@ -17,4 +16,6 @@ export const LocalSource = {
|
||||
TOUR_API: 2,
|
||||
KAKAO_LOCAL: 3,
|
||||
OFFICIAL_WEB: 4,
|
||||
LLM: 5,
|
||||
NAVER_CRAWL: 6,
|
||||
} as const;
|
||||
|
||||
@ -4,17 +4,17 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { MediaDataUnitId } from './mediaDataUnitId';
|
||||
import type { MediaDataOriginUrl } from './mediaDataOriginUrl';
|
||||
import type { SourceType } from './sourceType';
|
||||
import type { MediaDataSourceUrl } from './mediaDataSourceUrl';
|
||||
import type { MediaDataLabel } from './mediaDataLabel';
|
||||
import type { MediaDataAltText } from './mediaDataAltText';
|
||||
import type { MediaDataVisionConfidence } from './mediaDataVisionConfidence';
|
||||
import type { MediaStatus } from './mediaStatus';
|
||||
import type { MediaDataWidth } from './mediaDataWidth';
|
||||
import type { MediaDataHeight } from './mediaDataHeight';
|
||||
import type { MediaDataCreatedAt } from './mediaDataCreatedAt';
|
||||
import type { MediaDataUnitId } from "./mediaDataUnitId";
|
||||
import type { MediaDataOriginUrl } from "./mediaDataOriginUrl";
|
||||
import type { SourceType } from "./sourceType";
|
||||
import type { MediaDataSourceUrl } from "./mediaDataSourceUrl";
|
||||
import type { MediaDataLabel } from "./mediaDataLabel";
|
||||
import type { MediaDataAltText } from "./mediaDataAltText";
|
||||
import type { MediaDataVisionConfidence } from "./mediaDataVisionConfidence";
|
||||
import type { MediaStatus } from "./mediaStatus";
|
||||
import type { MediaDataWidth } from "./mediaDataWidth";
|
||||
import type { MediaDataHeight } from "./mediaDataHeight";
|
||||
import type { MediaDataCreatedAt } from "./mediaDataCreatedAt";
|
||||
|
||||
/**
|
||||
* 사진 1건.
|
||||
|
||||
@ -8,8 +8,7 @@
|
||||
/**
|
||||
* media.status 코드값. 비전 결과 신뢰도가 낮으면 자동 반영하지 않고 PENDING_REVIEW 로 둔다.
|
||||
*/
|
||||
export type MediaStatus = typeof MediaStatus[keyof typeof MediaStatus];
|
||||
|
||||
export type MediaStatus = (typeof MediaStatus)[keyof typeof MediaStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const MediaStatus = {
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCategory } from './placeCategory';
|
||||
import type { PlaceStatus } from './placeStatus';
|
||||
import type { MySiteDataRoadAddress } from './mySiteDataRoadAddress';
|
||||
import type { MySiteDataCreatedAt } from './mySiteDataCreatedAt';
|
||||
import type { MySiteDataSiteId } from './mySiteDataSiteId';
|
||||
import type { MySiteDataStatus } from './mySiteDataStatus';
|
||||
import type { MySiteDataDomain } from './mySiteDataDomain';
|
||||
import type { MySiteDataTemplateId } from './mySiteDataTemplateId';
|
||||
import type { MySiteDataPublishedAt } from './mySiteDataPublishedAt';
|
||||
import type { MySiteDataThumbnailUrl } from './mySiteDataThumbnailUrl';
|
||||
import type { PlaceCategory } from "./placeCategory";
|
||||
import type { PlaceStatus } from "./placeStatus";
|
||||
import type { MySiteDataRoadAddress } from "./mySiteDataRoadAddress";
|
||||
import type { MySiteDataCreatedAt } from "./mySiteDataCreatedAt";
|
||||
import type { MySiteDataSiteId } from "./mySiteDataSiteId";
|
||||
import type { MySiteDataStatus } from "./mySiteDataStatus";
|
||||
import type { MySiteDataDomain } from "./mySiteDataDomain";
|
||||
import type { MySiteDataTemplateId } from "./mySiteDataTemplateId";
|
||||
import type { MySiteDataPublishedAt } from "./mySiteDataPublishedAt";
|
||||
import type { MySiteDataThumbnailUrl } from "./mySiteDataThumbnailUrl";
|
||||
|
||||
/**
|
||||
* 내 사이트 목록의 한 줄 — 사업장(place) + 사이트(site).
|
||||
@ -33,7 +33,6 @@ export interface MySiteData {
|
||||
domain?: MySiteDataDomain;
|
||||
template_id?: MySiteDataTemplateId;
|
||||
published_at?: MySiteDataPublishedAt;
|
||||
/** 목록 카드의 그림. 발행에 성공해야 채워지고, 발행마다 `?v=` 가 바뀐다(site_thumbnail.public_url). */
|
||||
thumbnail_url?: MySiteDataThumbnailUrl;
|
||||
needs_rebuild?: boolean;
|
||||
}
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { SiteStatus } from './siteStatus';
|
||||
import type { SiteStatus } from "./siteStatus";
|
||||
|
||||
export type MySiteDataStatus = SiteStatus | null;
|
||||
|
||||
@ -4,15 +4,15 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCandidateExternalPlaceId } from './placeCandidateExternalPlaceId';
|
||||
import type { PlaceCandidateRoadAddress } from './placeCandidateRoadAddress';
|
||||
import type { PlaceCandidateAddress } from './placeCandidateAddress';
|
||||
import type { PlaceCandidatePhone } from './placeCandidatePhone';
|
||||
import type { PlaceCandidateLatitude } from './placeCandidateLatitude';
|
||||
import type { PlaceCandidateLongitude } from './placeCandidateLongitude';
|
||||
import type { PlaceCandidateCategoryName } from './placeCandidateCategoryName';
|
||||
import type { PlaceCandidatePlaceUrl } from './placeCandidatePlaceUrl';
|
||||
import type { PlaceCandidateNaverPlaceUrl } from './placeCandidateNaverPlaceUrl';
|
||||
import type { PlaceCandidateExternalPlaceId } from "./placeCandidateExternalPlaceId";
|
||||
import type { PlaceCandidateRoadAddress } from "./placeCandidateRoadAddress";
|
||||
import type { PlaceCandidateAddress } from "./placeCandidateAddress";
|
||||
import type { PlaceCandidatePhone } from "./placeCandidatePhone";
|
||||
import type { PlaceCandidateLatitude } from "./placeCandidateLatitude";
|
||||
import type { PlaceCandidateLongitude } from "./placeCandidateLongitude";
|
||||
import type { PlaceCandidateCategoryName } from "./placeCandidateCategoryName";
|
||||
import type { PlaceCandidatePlaceUrl } from "./placeCandidatePlaceUrl";
|
||||
import type { PlaceCandidateNaverPlaceUrl } from "./placeCandidateNaverPlaceUrl";
|
||||
|
||||
/**
|
||||
* 외부 장소 DB 에서 찾은 후보 1건. UI 가 이걸 카드로 그려 사람이 고른다.
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
* places.category 코드값. 업종 — 스키마 파일(common/category_schema/resources/*.json)과 1:1.
|
||||
업종 추가 = 여기에 코드 추가 + 스키마 파일 1개 추가.
|
||||
*/
|
||||
export type PlaceCategory = typeof PlaceCategory[keyof typeof PlaceCategory];
|
||||
|
||||
export type PlaceCategory = (typeof PlaceCategory)[keyof typeof PlaceCategory];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const PlaceCategory = {
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { LocalContentType } from "./localContentType";
|
||||
import type { PlaceContentDataBody } from "./placeContentDataBody";
|
||||
import type { PlaceContentDataDisplayEndAt } from "./placeContentDataDisplayEndAt";
|
||||
import type { PlaceContentDataCollectedAt } from "./placeContentDataCollectedAt";
|
||||
|
||||
/**
|
||||
* 업장 반경 주변정보 1건(admin 목록용).
|
||||
*/
|
||||
export interface PlaceContentData {
|
||||
place_content_id: string;
|
||||
place_id: string;
|
||||
content_type: LocalContentType;
|
||||
external_id: string;
|
||||
title: string;
|
||||
body: PlaceContentDataBody;
|
||||
distance_m: number;
|
||||
has_image: boolean;
|
||||
hidden: boolean;
|
||||
display_end_at?: PlaceContentDataDisplayEndAt;
|
||||
collected_at?: PlaceContentDataCollectedAt;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type PlaceContentDataBody = { [key: string]: unknown };
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type PlaceContentDataCollectedAt = string | null;
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type PlaceContentDataDisplayEndAt = string | null;
|
||||
@ -4,20 +4,20 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCategory } from './placeCategory';
|
||||
import type { PlaceStatus } from './placeStatus';
|
||||
import type { PlaceDataOwnerUserId } from './placeDataOwnerUserId';
|
||||
import type { PlaceDataExternalSource } from './placeDataExternalSource';
|
||||
import type { PlaceDataExternalPlaceId } from './placeDataExternalPlaceId';
|
||||
import type { PlaceDataRoadAddress } from './placeDataRoadAddress';
|
||||
import type { PlaceDataAddress } from './placeDataAddress';
|
||||
import type { PlaceDataPhone } from './placeDataPhone';
|
||||
import type { PlaceDataLatitude } from './placeDataLatitude';
|
||||
import type { PlaceDataLongitude } from './placeDataLongitude';
|
||||
import type { PlaceDataRegionCode } from './placeDataRegionCode';
|
||||
import type { PlaceDataVerifiedAt } from './placeDataVerifiedAt';
|
||||
import type { PlaceDataContentUpdatedAt } from './placeDataContentUpdatedAt';
|
||||
import type { PlaceDataCreatedAt } from './placeDataCreatedAt';
|
||||
import type { PlaceCategory } from "./placeCategory";
|
||||
import type { PlaceStatus } from "./placeStatus";
|
||||
import type { PlaceDataOwnerUserId } from "./placeDataOwnerUserId";
|
||||
import type { PlaceDataExternalSource } from "./placeDataExternalSource";
|
||||
import type { PlaceDataExternalPlaceId } from "./placeDataExternalPlaceId";
|
||||
import type { PlaceDataRoadAddress } from "./placeDataRoadAddress";
|
||||
import type { PlaceDataAddress } from "./placeDataAddress";
|
||||
import type { PlaceDataPhone } from "./placeDataPhone";
|
||||
import type { PlaceDataLatitude } from "./placeDataLatitude";
|
||||
import type { PlaceDataLongitude } from "./placeDataLongitude";
|
||||
import type { PlaceDataRegionCode } from "./placeDataRegionCode";
|
||||
import type { PlaceDataVerifiedAt } from "./placeDataVerifiedAt";
|
||||
import type { PlaceDataContentUpdatedAt } from "./placeDataContentUpdatedAt";
|
||||
import type { PlaceDataCreatedAt } from "./placeDataCreatedAt";
|
||||
|
||||
export interface PlaceData {
|
||||
place_id: string;
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ExternalPlaceSource } from './externalPlaceSource';
|
||||
import type { ExternalPlaceSource } from "./externalPlaceSource";
|
||||
|
||||
export type PlaceDataExternalSource = ExternalPlaceSource | null;
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceSearchItemRoadAddress } from './placeSearchItemRoadAddress';
|
||||
import type { PlaceSearchItemCategoryName } from './placeSearchItemCategoryName';
|
||||
import type { PlaceSearchItemCategory } from './placeSearchItemCategory';
|
||||
import type { PlaceSearchItemRoadAddress } from "./placeSearchItemRoadAddress";
|
||||
import type { PlaceSearchItemCategoryName } from "./placeSearchItemCategoryName";
|
||||
import type { PlaceSearchItemCategory } from "./placeSearchItemCategory";
|
||||
import type { PlaceSearchItemNaverPlaceUrl } from "./placeSearchItemNaverPlaceUrl";
|
||||
|
||||
/**
|
||||
* 공개 검색 결과 1건.
|
||||
@ -21,6 +22,5 @@ export interface PlaceSearchItem {
|
||||
road_address?: PlaceSearchItemRoadAddress;
|
||||
category_name?: PlaceSearchItemCategoryName;
|
||||
category?: PlaceSearchItemCategory;
|
||||
/** @nullable */
|
||||
naver_place_url?: string | null;
|
||||
naver_place_url?: PlaceSearchItemNaverPlaceUrl;
|
||||
}
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCategory } from './placeCategory';
|
||||
import type { PlaceCategory } from "./placeCategory";
|
||||
|
||||
export type PlaceSearchItemCategory = PlaceCategory | null;
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
* places.status 코드값. 사업장 생애주기.
|
||||
해지는 삭제가 아니라 SUSPENDED 로의 상태 전이다(색인된 페이지를 갑자기 404 로 만들지 않는다).
|
||||
*/
|
||||
export type PlaceStatus = typeof PlaceStatus[keyof typeof PlaceStatus];
|
||||
|
||||
export type PlaceStatus = (typeof PlaceStatus)[keyof typeof PlaceStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const PlaceStatus = {
|
||||
|
||||
@ -8,8 +8,7 @@
|
||||
/**
|
||||
* publish_logs.action 코드값.
|
||||
*/
|
||||
export type PublishAction = typeof PublishAction[keyof typeof PublishAction];
|
||||
|
||||
export type PublishAction = (typeof PublishAction)[keyof typeof PublishAction];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const PublishAction = {
|
||||
@ -18,4 +17,5 @@ export const PublishAction = {
|
||||
REBUILD: 3,
|
||||
SUSPEND: 4,
|
||||
RESUME: 5,
|
||||
ROLLBACK: 6,
|
||||
} as const;
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PublishAction } from './publishAction';
|
||||
import type { PublishResult } from './publishResult';
|
||||
import type { PublishLogDataRejectReason } from './publishLogDataRejectReason';
|
||||
import type { PublishLogDataDetail } from './publishLogDataDetail';
|
||||
import type { PublishLogDataCreatedAt } from './publishLogDataCreatedAt';
|
||||
import type { PublishAction } from "./publishAction";
|
||||
import type { PublishResult } from "./publishResult";
|
||||
import type { PublishLogDataRejectReason } from "./publishLogDataRejectReason";
|
||||
import type { PublishLogDataDetail } from "./publishLogDataDetail";
|
||||
import type { PublishLogDataCreatedAt } from "./publishLogDataCreatedAt";
|
||||
|
||||
export interface PublishLogData {
|
||||
publish_log_id: string;
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PublishRejectReason } from './publishRejectReason';
|
||||
import type { PublishRejectReason } from "./publishRejectReason";
|
||||
|
||||
export type PublishLogDataRejectReason = PublishRejectReason | null;
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
/**
|
||||
* publish_logs.reject_reason 코드값. 검수 게이트가 발행을 막은 이유(절대규칙 1~3).
|
||||
*/
|
||||
export type PublishRejectReason = typeof PublishRejectReason[keyof typeof PublishRejectReason];
|
||||
|
||||
export type PublishRejectReason =
|
||||
(typeof PublishRejectReason)[keyof typeof PublishRejectReason];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const PublishRejectReason = {
|
||||
|
||||
@ -8,8 +8,7 @@
|
||||
/**
|
||||
* publish_logs.result 코드값.
|
||||
*/
|
||||
export type PublishResult = typeof PublishResult[keyof typeof PublishResult];
|
||||
|
||||
export type PublishResult = (typeof PublishResult)[keyof typeof PublishResult];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const PublishResult = {
|
||||
|
||||
14
solution/frontend/src/api/generated/model/relayParams.ts
Normal file
14
solution/frontend/src/api/generated/model/relayParams.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type RelayParams = {
|
||||
/**
|
||||
* @minLength 8
|
||||
* @maxLength 2000
|
||||
*/
|
||||
url: string;
|
||||
};
|
||||
@ -4,9 +4,9 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { RenderStatusDataRenderedAt } from './renderStatusDataRenderedAt';
|
||||
import type { RenderStatusDataRenderedVersion } from './renderStatusDataRenderedVersion';
|
||||
import type { RenderStatusDataError } from './renderStatusDataError';
|
||||
import type { RenderStatusDataRenderedAt } from "./renderStatusDataRenderedAt";
|
||||
import type { RenderStatusDataRenderedVersion } from "./renderStatusDataRenderedVersion";
|
||||
import type { RenderStatusDataError } from "./renderStatusDataError";
|
||||
|
||||
/**
|
||||
* 정적 페이지가 실제로 구워졌는지. 프리렌더가 남긴 보고서를 그대로 옮긴다.
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqCreateFaqSortOrder } from './reqCreateFaqSortOrder';
|
||||
import type { ReqCreateFaqSortOrder } from "./reqCreateFaqSortOrder";
|
||||
|
||||
/**
|
||||
* 사장님이 직접 쓴 FAQ.
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { LinkChannel } from './linkChannel';
|
||||
import type { ReqCreateLinkTitle } from './reqCreateLinkTitle';
|
||||
import type { SourceType } from './sourceType';
|
||||
import type { LinkChannel } from "./linkChannel";
|
||||
import type { ReqCreateLinkTitle } from "./reqCreateLinkTitle";
|
||||
import type { SourceType } from "./sourceType";
|
||||
|
||||
export interface ReqCreateLink {
|
||||
channel?: LinkChannel;
|
||||
|
||||
@ -4,11 +4,9 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PlaceCategory } from './placeCategory';
|
||||
import type { ReqCreatePlaceOwnerUserId } from './reqCreatePlaceOwnerUserId';
|
||||
import type { PlaceCategory } from "./placeCategory";
|
||||
|
||||
export interface ReqCreatePlace {
|
||||
name?: string;
|
||||
category?: PlaceCategory;
|
||||
owner_user_id?: ReqCreatePlaceOwnerUserId;
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ReqHidePlaceContent {
|
||||
hidden: boolean;
|
||||
}
|
||||
18
solution/frontend/src/api/generated/model/reqRollback.ts
Normal file
18
solution/frontend/src/api/generated/model/reqRollback.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 예전 버전으로 공개 주소를 되돌린다.
|
||||
|
||||
★ 재굽기가 아니다 — 대상 버전이 디스크에 아직 있으면 심볼릭 링크만 돌린다. 지워졌으면
|
||||
(보관 정책, prerender.ts pruneOldVersions) site_versions.snapshot 으로 다시 굽고 나서
|
||||
돌린다. 어느 경우든 게이트를 다시 통과해야 한다(사장님이 이미 확인한 값이라 대개는
|
||||
그대로 통과한다).
|
||||
*/
|
||||
export interface ReqRollback {
|
||||
target_version: number;
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqSignupName } from './reqSignupName';
|
||||
import type { ReqSignupName } from "./reqSignupName";
|
||||
|
||||
/**
|
||||
* id/pw 가입. 가입 = 계정 1개다.
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { PublishAction } from './publishAction';
|
||||
import type { PublishAction } from "./publishAction";
|
||||
|
||||
/**
|
||||
* 발행 상태 전이. ★ 해지는 삭제가 아니라 상태 전이다 —
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqSiteThemeTheme } from './reqSiteThemeTheme';
|
||||
import type { ReqSiteThemeTheme } from "./reqSiteThemeTheme";
|
||||
|
||||
/**
|
||||
* 디자인(색·서체·섹션) 저장. 에디터 좌측 패널과 [디자인] 탭이 만든 결과 그대로 온다.
|
||||
|
||||
@ -10,4 +10,6 @@
|
||||
|
||||
★ 확인된 fact 만 근거로 쓴다. 근거가 없으면 생성하지 않는다(유료 호출조차 안 한다).
|
||||
*/
|
||||
export interface ReqStartCopy { [key: string]: unknown }
|
||||
export interface ReqStartCopy {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Generated by orval v7.21.0 🍺
|
||||
* Do not edit manually.
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ReqSyncFestivalsAreaCode } from './reqSyncFestivalsAreaCode';
|
||||
import type { ReqSyncFestivalsStartDate } from './reqSyncFestivalsStartDate';
|
||||
|
||||
export interface ReqSyncFestivals {
|
||||
/**
|
||||
* @minLength 1
|
||||
* @maxLength 10
|
||||
*/
|
||||
region_code: string;
|
||||
area_code?: ReqSyncFestivalsAreaCode;
|
||||
start_date?: ReqSyncFestivalsStartDate;
|
||||
}
|
||||
@ -4,8 +4,8 @@
|
||||
* Web4Ai API
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { FactStatus } from './factStatus';
|
||||
import type { ReqTransitionFactValue } from './reqTransitionFactValue';
|
||||
import type { FactStatus } from "./factStatus";
|
||||
import type { ReqTransitionFactValue } from "./reqTransitionFactValue";
|
||||
|
||||
/**
|
||||
* 검증 상태 전이. 허용 전이는 FACT_STATUS_TRANSITIONS 가 유일한 소스다.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user