Skip to content

[FEAT] Swagger 기반 API 클라이언트 자동 생성 도입 (orval)#146

Merged
kimminna merged 11 commits into
developfrom
feat/web/145-orval-api-client-codegen
Jul 12, 2026
Merged

[FEAT] Swagger 기반 API 클라이언트 자동 생성 도입 (orval)#146
kimminna merged 11 commits into
developfrom
feat/web/145-orval-api-client-codegen

Conversation

@kimminna

Copy link
Copy Markdown
Member

ISSUE 🔗

close #145



What is this PR? 🔍

Swagger(OpenAPI) 스펙에서 API 클라이언트(axios 함수 + React Query 훅)와 Zod 검증 스키마를 자동 생성하는 파이프라인(orval)을 도입했습니다.

배경

  • 기존 구조: API 엔드포인트를 apps/timo-web/api/endpoint.ts에 문자열 상수로 손으로 하나씩 관리하고, 요청/응답 타입과 유효성 검증은 기능을 구현할 때마다 개별적으로 작성했습니다.
  • 발생 문제: 백엔드 스펙이 바뀌어도 프론트에서 자동으로 감지되지 않아 타입 불일치나 필드 누락이 런타임에야 발견됐고, 엔드포인트마다 요청 바디 검증 로직을 새로 작성해야 했습니다.
  • 해결 방향: orval로 live Swagger 스펙에서 axios 호출 함수·React Query 훅·Zod 검증 스키마를 한 번에 생성해, 백엔드 스펙과 프론트 코드가 항상 같은 소스에서 동기화되도록 했습니다.

코드 생성 파이프라인 (orval)

  • 변경 요약: orval.config.ts에서 하나의 live 스펙으로 두 가지 산출물(axios+React Query 클라이언트, Zod 스키마)을 함께 생성하도록 설정했습니다.
  • 이유: swagger-typescript-api 등 다른 생성기는 타입/axios 클라이언트만 만들고, 프로젝트가 이미 쓰고 있는 Zod 검증 스키마는 별도로 손으로 관리해야 해서 결국 "생성된 타입"과 "직접 짠 검증 스키마" 이중 유지보수 문제가 생깁니다. orval은 같은 OpenAPI 스펙에서 axios/React Query 훅과 Zod 스키마를 함께 생성할 수 있어 이 문제를 피할 수 있습니다.
  • 구현 방식: NEXT_PUBLIC_API_BASE_URL(기존 axios 인스턴스가 쓰는 것과 동일한 env) + /v3/api-docs로 스펙 URL을 만들고, pnpm run gen:api(orval --config ./orval.config.ts)를 실행하면 api/generated/endpoints/<도메인>/*.ts(axios 함수 + useXxx React Query 훅), *.zod.ts(Zod 스키마), api/generated/models/*.ts(타입)가 생성됩니다. output.override.mutator로 기존 axios 인스턴스(api/client/axios.ts, baseURL + 에러 파싱 인터셉터 포함)를 감싼 customInstance를 연결해서 런타임 설정을 그대로 재사용합니다. hooks.afterAllFilesWrite에 prettier → eslint --fix를 걸어서 생성 직후 코드가 항상 프로젝트 lint 규칙을 통과하도록 했습니다.
  • 경계 · 제약: api/generated/**는 "Do not edit manually" 생성 코드이며, 스펙이 바뀌면 직접 고치지 말고 pnpm run gen:api로 재생성해야 합니다. 백엔드 스펙의 bearerAuth 시큐리티 스킴에 스펙상 허용되지 않는 name 필드가 있어(springdoc-openapi 출력 특성) unsafeDisableValidation: true로 검증을 우회하고 있습니다(orval#3203).

사용 예시

  • 변경 요약: 훅 하나만 import하면 요청/응답 타입, axios 호출, React Query 캐싱까지 한 번에 처리됩니다.
  • 이유: 기존에는 컴포넌트에서 API를 하나 쓰려면 엔드포인트 상수 확인 → axios 호출 함수 작성 → 요청/응답 타입 작성 → Query key 관리를 매번 따로 해야 했습니다.
  • 구현 방식: 예를 들어 AI 예상 소요시간 추천 API를 쓰려면 다음과 같이 훅만 가져오면 됩니다.
import { useRecommendDuration } from "@/api/generated/endpoints/ai/ai";

function TodoForm() {
  const { mutate, isPending } = useRecommendDuration();

  const handleSubmit = (title: string, tagId?: number) => {
    mutate({ data: { title, tagId } });
  };
}

서버로 보내기 전에 값을 검증하고 싶다면 같은 스펙에서 생성된 Zod 스키마를 함께 씁니다.

import { RecommendDurationBody } from "@/api/generated/endpoints/ai/ai.zod";

const parsed = RecommendDurationBody.safeParse({ title, tagId });
if (!parsed.success) {
  // 폼 에러 처리
}

GET 요청도 동일한 패턴입니다. 각 도메인 파일(api/generated/endpoints/<도메인>/<도메인>.ts)에서 use로 시작하는 export(useGetHome, useGetTodoDetail, useGetTags 등)를 찾아 그대로 쓰면 됩니다.

기대 효과

  • 변경 요약: 스펙-프론트 간 drift를 구조적으로 줄이고, 새 API를 연동할 때 반복 작업을 없앴습니다.
  • 이유: 아래 문제들이 실제로 줄어듭니다.
    • 백엔드가 필드를 추가·삭제·이름 변경하면 pnpm run gen:api 한 번으로 타입 에러가 즉시 드러나 컴파일 타임에 발견됩니다(기존엔 런타임에야 발견).
    • 새 엔드포인트를 쓸 때 "엔드포인트 상수 작성 → axios 호출 함수 작성 → 타입 작성 → 검증 스키마 작성" 4단계가 사라지고 생성된 훅을 import하는 것으로 끝납니다.
    • 요청 바디 검증(min/max, enum 등)이 백엔드 문서에 적힌 제약과 항상 일치합니다.

정리한 코드

  • 변경 요약: 손으로 관리하던 api/endpoint.ts(엔드포인트 상수 모음)와, 아무 곳에서도 참조되지 않던 apiResponseSchema 제네릭 래퍼 함수를 제거했습니다.
  • 이유: orval이 생성한 훅과 타입이 이를 완전히 대체하며, 죽은 코드를 남겨두면 앞으로 어떤 걸 써야 하는지 혼동을 줍니다.
  • 구현 방식: Grep으로 두 심볼 모두 다른 파일에서 참조되지 않음을 확인한 뒤 제거했습니다. 에러 파싱에 쓰이는 apiErrorSchema/ApiErrorResponse는 계속 사용 중이라 유지했습니다.



To Reviewers

api/generated/**은 diff가 크지만 사람이 직접 작성한 코드가 아닙니다. 리뷰는 orval.config.ts, api/client/custom-instance.ts, scripts/fix-generated-escapes.js 위주로 봐주시면 충분합니다.

unsafeDisableValidation: true는 백엔드 스펙의 사소한 오류(bearerAuth의 불필요한 name 필드)를 우회하기 위한 조치입니다. 백엔드에서 시큐리티 스킴을 고치면 이 옵션은 제거할 수 있습니다.

NEXT_PUBLIC_API_BASE_URL.env.local(gitignore 대상)에만 있어서, 다른 팀원이나 CI에서 pnpm run gen:api를 돌리려면 각자 이 값을 설정해야 합니다. 현재 어떤 컴포넌트도 아직 생성된 훅을 실제로 사용하고 있지 않아, 실제 화면 동작 확인은 이 PR 범위 밖이며 후속 작업에서 진행됩니다.



Screenshot 📷



Test Checklist ✔

  • pnpm lint 통과 (apps/timo-web)
  • pnpm check-types 통과 (기존에 있던 무관한 아이콘 export 에러 2건 제외)
  • pnpm run gen:api 재실행 후 생성 결과가 lint/타입체크를 다시 통과하는지 확인
  • pnpm build — 미실행: CI에서 확인 예정
  • 실제 화면에서 생성된 훅(useRecommendDuration 등) 호출 동작 확인 — 후속 작업 (현재 사용하는 컴포넌트 없음)

kimminna added 3 commits July 11, 2026 22:01
- Storybook vite가 끌어온 esbuild 0.28.1 postinstall 충돌을 피하기 위해 pnpm.overrides.esbuild를 0.25.12로 고정했습니다
- apps/timo-web에 orval, @next/env devDependency와 gen:api 스크립트를 추가했습니다
- orval.config.ts에서 live 스펙(NEXT_PUBLIC_API_BASE_URL 기반)으로 react-query(axios) 클라이언트와 zod 검증 스키마를 함께 생성하도록 설정했습니다
- custom-instance.ts로 기존 axios 인스턴스(baseURL + 에러 파싱 인터셉터)를 orval mutator 시그니처에 연결했습니다
- fix-generated-escapes.js로 생성 코드의 불필요한 이스케이프 문자를 정리해 lint를 통과하게 했습니다
- 12개 도메인(AI/Auth/Focus/Home/Onboarding/Statistics/Tag/Terms/TimeBox/Timer/Todo/User)의 axios 함수, React Query 훅, Zod 스키마, 타입을 생성했습니다
- orval이 응답 타입을 BaseResponseXxx 형태로 직접 생성해 아무 곳에서도 참조하지 않던 제네릭 apiResponseSchema 래퍼 함수를 제거했습니다
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 12, 2026 7:17am

Request Review

@github-actions github-actions Bot requested review from ehye1 and jjangminii July 11, 2026 13:08
@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kimminna, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7d54ea89-d6c1-45e7-b05c-b71132dcf6a0

📥 Commits

Reviewing files that changed from the base of the PR and between d23ff2f and 11fe4cb.

📒 Files selected for processing (1)
  • apps/timo-web/api/todo/todo-schema.ts

Walkthrough

Swagger/OpenAPI 스펙 기반 Axios/React Query 및 Zod 클라이언트 생성 구성을 추가했습니다. Axios mutator와 후처리 도구를 연결하고, 요청별 로케일 설정 및 일부 훅의 서식을 조정했습니다.

Changes

OpenAPI 클라이언트 자동 생성

Layer / File(s) Summary
Axios mutator 및 응답 계약
apps/timo-web/api/client/custom-instance.ts, apps/timo-web/api/schema/response.ts
생성 클라이언트용 customInstance와 Axios 관련 타입 별칭을 추가하고, 기존 응답 래퍼 스키마를 제거했습니다.
OpenAPI 기반 이중 생성 구성
apps/timo-web/orval.config.ts
환경 변수에서 OpenAPI 스펙 URL을 구성하고 React Query/Axios 및 Zod 생성 타깃과 후처리 명령을 설정했습니다.
생성 명령 및 후처리 도구
apps/timo-web/package.json, apps/timo-web/scripts/fix-generated-escapes.js, package.json
API 생성·타입 검사 스크립트, 생성 결과의 이스케이프 정리 도구, esbuild 버전 오버라이드를 추가했습니다.

요청 로케일 설정

Layer / File(s) Summary
RootLayout 요청 로케일 연결
apps/timo-web/app/[locale]/layout.tsx
유효한 로케일을 확인한 뒤 setRequestLocale(locale)을 호출하도록 변경했습니다.

Todo 훅 서식 조정

Layer / File(s) Summary
Todo 훅 서식 조정
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/*
반복·태그·시간·아이콘 훅의 공백과 줄바꿈 배치를 조정했습니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Environment
  participant Orval
  participant OpenAPISpec
  participant GeneratedClient
  participant Axios
  Environment->>Orval: API base URL 제공
  Orval->>OpenAPISpec: /v3/api-docs 요청
  OpenAPISpec-->>Orval: OpenAPI 스펙 반환
  Orval->>GeneratedClient: Axios/React Query 및 Zod 코드 생성
  GeneratedClient->>Axios: customInstance로 API 요청
  Axios-->>GeneratedClient: 응답 데이터 반환
Loading

Possibly related PRs

Suggested labels: 🛠️ Setup

Suggested reviewers: yumin-kim2, jjangminii, ehye1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 앱 로케일 layout의 setRequestLocale 추가는 API 자동 생성 작업과 직접 관련이 없어 보입니다. 해당 locale layout 변경은 별도 PR로 분리하거나, orval 도입과의 직접적인 필요성을 코드 주석/설명으로 보강하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 Swagger/orval 기반 API 자동 생성 도입이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 설명이 orval 도입 배경, 구현, 검증 내용을 변경사항과 잘 맞게 설명합니다.
Linked Issues check ✅ Passed customInstance, orval 설정, 후처리 스크립트, 스크립트 추가, esbuild 오버라이드가 요구사항을 충족합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/145-orval-api-client-codegen

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-12 07:13 UTC

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale]/home 164.38 kB 🔴 370.20 kB
/[locale]/today 62.26 kB 🟡 268.08 kB
/[locale]/focus 58.84 kB 🟡 264.66 kB
/[locale]/settings/account 0 B 🟡 205.81 kB
/[locale]/settings 0 B 🟡 205.81 kB
/[locale]/settings/policy 0 B 🟡 205.81 kB
/[locale]/statistics 57.82 kB 🟡 263.64 kB
/[locale]/[...rest] 0 B 🟡 205.81 kB
/[locale]/onboarding 109.29 kB 🟡 315.10 kB
/[locale] 0 B 🟡 205.81 kB

공유 번들: 205.81 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web
URL Perf A11y LCP CLS TBT
/en/home 🔴 65 🟢 96 🔴 14.4s 🟢 0.000 🟡 421ms
/en/today 🟡 73 🟡 93 🔴 13.8s 🟢 0.000 🟢 144ms
/en/focus 🟡 70 🟡 91 🔴 13.8s 🟢 0.000 🟡 257ms
/en/statistics 🟡 70 🟢 96 🔴 13.8s 🟢 0.000 🟡 213ms

Perf ≥ 70 / A11y ≥ 85 목표
LCP 🟢 < 2.5s 🟡 < 4s 🔴 ≥ 4s  |  CLS 🟢 < 0.1 🟡 < 0.25 🔴 ≥ 0.25  |  TBT 🟢 < 200ms 🟡 < 600ms 🔴 ≥ 600ms

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: 1501013

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/timo-web/api/client/custom-instance.ts`:
- Line 11: ErrorType<TError>를 실제 인터셉터가 reject하는 parseApiError() 결과인 ApiError 형태와
일치하도록 수정하세요. ApiError가 서버 에러 payload를 포함한다면 해당 필드를 명시적으로 연결하고, onError에서
response?.data에 의존하는 기존 타입 가정이 실제 런타임 구조와 맞도록 조정하세요.

In `@apps/timo-web/orval.config.ts`:
- Line 20: Add a TODO comment next to unsafeDisableValidation in the Orval
configuration documenting that it is a temporary workaround for the bearerAuth
issue and should be removed once the upstream Orval issue is resolved, allowing
spec validation to be re-enabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f805e154-03d7-4e45-98b4-243b8610790f

📥 Commits

Reviewing files that changed from the base of the PR and between 9552e20 and a3fb42c.

⛔ Files ignored due to path filters (136)
  • apps/timo-web/api/generated/endpoints/ai/ai.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/ai/ai.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/auth/auth.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/auth/auth.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/focus/focus.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/focus/focus.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/home/home.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/home/home.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/onboarding/onboarding.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/onboarding/onboarding.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/statistics/statistics.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/statistics/statistics.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/tag/tag.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/tag/tag.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/terms/terms.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/terms/terms.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/time-box/time-box.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/time-box/time-box.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/timer/timer.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/timer/timer.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/todo/todo.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/todo/todo.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/user/user.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/endpoints/user/user.zod.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/authReissueResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/authTokenRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/authTokenResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseAuthReissueResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseAuthTokenResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseFocusTodoResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseHomeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseListTimeBoxResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseObject.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseRecommendDurationResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseStatisticsCalendarResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseStatisticsDailyResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseStatisticsSummaryResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseSubtaskStatusChangeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTagCreateResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTagListResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTermsListResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTimerActiveResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTimerExtendResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTimerFinishResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTimerStartResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTimerStatusResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTodayResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTodoCreateResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTodoDetailResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTodoReorderResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseTodoStatusChangeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseUpdateLanguageResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseUpdateTimezoneResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseUserProfileResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/baseResponseVoid.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/dailyTodoResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/dayCompletionResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/dayResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/dayResponseDayOfWeek.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/errorDto.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/focusTodoDetailResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/focusTodoResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/focusTodoResponseDayOfWeek.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getCalendarParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getDailyParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getHomeParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getSummaryParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getTermsParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getTimeBoxesParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/getTodoDetailParams.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/homeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/homeResponseFilter.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/index.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/onboardingRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/onboardingRequestLanguage.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/recommendDurationRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/recommendDurationResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/repeatResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/repeatResponseWeekdaysItem.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/statisticsCalendarResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/statisticsDailyResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/statisticsSummaryResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/subtaskResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/subtaskStatusChangeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/subtaskStatusUpdateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/tagCreateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/tagCreateResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/tagListResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/tagResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/termsListResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/termsResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timeBoxResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timeBoxResponseEndAction.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timeBoxResponseStartAction.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerActionRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerActionRequestAction.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerActiveResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerExtendRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerExtendResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerFinishResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerStartResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/timerStatusResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todayResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todayResponseDayOfWeek.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todayTodoResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todayTodoResponseTimerStatus.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateRequestIcon.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateRequestPriority.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateRequestRepeatType.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateRequestRepeatWeekdaysItem.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoCreateResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoDetailResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoDetailResponseTimerStatus.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoReorderRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoReorderResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoResponseTimerStatus.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoStatusChangeResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoStatusUpdateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoSubtaskUpdateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoUpdateRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoUpdateRequestIcon.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoUpdateRequestPriority.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoUpdateRequestRepeatType.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/todoUpdateRequestRepeatWeekdaysItem.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateLanguageRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateLanguageRequestLanguage.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateLanguageResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateLanguageResponseLanguage.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateTimezoneRequest.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/updateTimezoneResponse.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/userInfo.ts is excluded by !**/generated/**
  • apps/timo-web/api/generated/models/userProfileResponse.ts is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/timo-web/api/client/custom-instance.ts
  • apps/timo-web/api/schema/response.ts
  • apps/timo-web/orval.config.ts
  • apps/timo-web/package.json
  • apps/timo-web/scripts/fix-generated-escapes.js
  • package.json
💤 Files with no reviewable changes (1)
  • apps/timo-web/api/schema/response.ts

Comment thread apps/timo-web/api/client/custom-instance.ts
Comment thread apps/timo-web/orval.config.ts
- bearerAuth 검증 우회가 임시 조치임을 명시하고, upstream orval 이슈(#3203)가 해결되면 스펙 검증을 다시 켜야 한다는 TODO를 timo/timoZod 두 타깃 모두에 추가했습니다
- setRequestLocale(locale)을 호출하지 않아 next-intl이 매 요청마다 headers()를 읽으면서 전체 라우트가 강제로 dynamic 렌더링되던 문제를 고쳤습니다
- 이로써 9개 페이지가 자동으로 정적(SSG) 렌더링되어 Vercel 서버리스 함수 개수 제한 문제도 함께 해소됩니다
- en/ko 외 다른 locale 값에 대한 on-demand 렌더링 fallback을 막아서, SSG 페이지마다 Vercel이 여분으로 붙이던 Prerender Function을 제거했습니다
kimminna added 2 commits July 12, 2026 14:43
- dynamicParams = false가 실제 Vercel 함수 개수 문제를 해결하지 못했고, 로컬 vercel build 검증 중 /en/focus 같은 SSG 라우트의 lambda 매핑이 깨지는 버그까지 유발해서 되돌렸습니다
- settings/account, settings/policy 페이지는 아직 빈 스텁이라 기능 손실 없이 제거했습니다
- 추후 /settings?tab= 쿼리 파라미터 방식으로 통합할 예정이라 경로 기반 하위 라우트를 미리 정리했습니다
- 라우트 파일 수가 줄어 Vercel 서버리스 함수 개수 절감에도 도움이 됩니다

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우와 이거 한 번으로 엔드포인트랑 타입이랑 훅이랑 zod 검증 스키마까지 한 번에 뽑아주다니‼️
수고하셨습니다~~

…to feat/web/145-orval-api-client-codegen
apiResponseSchema는 #145에서 미사용으로 제거됐으나 todo-schema.ts가
여전히 참조하고 있어 Build/Type Check가 실패했습니다. 어디서도
쓰이지 않는 createTodoResponseSchema/CreateTodoResponse도 함께 제거했습니다.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx:
- Around line 19-22: Rename the union type alias PresetTagName to
PresetTagNameTypes to match the project naming convention, and update all
references to the alias, including the TAG_ID_BY_NAME declaration and any
related usages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 206a21d8-bbe6-48df-98b2-a2803c92479b

📥 Commits

Reviewing files that changed from the base of the PR and between a5542da and d23ff2f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.ts
  • apps/timo-web/app/[locale]/layout.tsx
  • apps/timo-web/package.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx:
- Around line 19-22: Rename the union type alias PresetTagName to
PresetTagNameTypes to match the project naming convention, and update all
references to the alias, including the TAG_ID_BY_NAME declaration and any
related usages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 206a21d8-bbe6-48df-98b2-a2803c92479b

📥 Commits

Reviewing files that changed from the base of the PR and between a5542da and d23ff2f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.ts
  • apps/timo-web/app/[locale]/layout.tsx
  • apps/timo-web/package.json
🛑 Comments failed to post (1)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx (1)

19-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

타입 별칭 이름을 컨벤션에 맞춰 주세요.

PresetTagName은 유니언 타입 별칭이므로 PresetTagNameTypes처럼 Types 접미사를 사용해야 합니다. 관련 참조도 함께 변경해 주세요. TypeScript 타입 별칭 공식 문서도 참고할 수 있습니다. (typescriptlang.org)

수정 예시
-type PresetTagName = (typeof TAG_NAMES)[number];
+type PresetTagNameTypes = (typeof TAG_NAMES)[number];

-const TAG_ID_BY_NAME: Record<PresetTagName, number> = {
+const TAG_ID_BY_NAME: Record<PresetTagNameTypes, number> = {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

type PresetTagNameTypes = (typeof TAG_NAMES)[number];

// 태그 관리 API가 아직 없어, 목데이터 용도로 태그 이름 순서에 고정 tagId를 매핑한다.
const TAG_ID_BY_NAME: Record<PresetTagNameTypes, number> = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx
around lines 19 - 22, Rename the union type alias PresetTagName to
PresetTagNameTypes to match the project naming convention, and update all
references to the alias, including the TAG_ID_BY_NAME declaration and any
related usages.

Source: Path instructions

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To Reviewers에 리뷰 범위를 명확히 안내해주셔서 리뷰 시간이 많이 절약됐어요~ 저도 Zod로 검증하는 건 처음 봐서 사용 방법이랑 왜 이렇게 설계하셨는지 많이 배워갑니다 😊
하나의 OpenAPI 스펙에서 axios 함수 + React Query 훅(타입용) + Zod 스키마(런타임 검증용)를 한 번에 생성해서, "타입 따로 검증 로직 따로" 관리하던 이중 유지보수 문제를 구조적으로 없애신 게 인상 깊었어요. 기존 axios 인프라 재사용하신 점, 우회 조치에 근거 남겨주신 점, 죽은 코드 확인 후 제거하신 것까지 전반적으로 신중하게 처리하셨네요 👍

@kimminna kimminna merged commit cb5bf23 into develop Jul 12, 2026
14 checks passed
@kimminna kimminna deleted the feat/web/145-orval-api-client-codegen branch July 12, 2026 07:33
@kimminna kimminna mentioned this pull request Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Swagger 기반 API 클라이언트 자동 생성 도입 (orval)

3 participants