From c488a4b8d6e1c4f7267964cf9250f9d76ac8d976 Mon Sep 17 00:00:00 2001 From: senke Date: Sat, 25 Apr 2026 23:25:43 +0200 Subject: [PATCH] refactor(web): migrate authService partial to orval (v1.0.8 B6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth feature-service migration after dashboard / profile / playlist / track. Replaces 4 of 9 raw apiClient calls in @/features/auth/services/authService.ts with orval-generated functions from services/generated/auth/auth.ts. Functions migrated (4): - login → postAuthLogin - logout → postAuthLogout (empty body) - resendVerificationEmail → postAuthResendVerification - checkUsernameAvailability → getAuthCheckUsername Functions deliberately NOT migrated (5, deferred v1.0.9 — would need backend annotation fixes or careful prod verification before changing the wire shape on critical auth paths): - register — backend DTO `register_request.go:8` declares `json:"password_confirmation"` but the frontend currently sends `password_confirm`. orval-typed body would force the rename, which is the correct shape per the swaggo spec but a behaviour change on a critical path. Needs a separate validation pass against staging before flipping. - refreshToken — same drift: backend DTO uses `refresh_token`, frontend uses `refreshToken`. Identical risk profile. - requestPasswordReset / resetPassword — endpoints not yet annotated in swaggo (no /auth/password/* paths in openapi.yaml). Backend annotation extension required first. - verifyEmail — verb drift (frontend GET /auth/verify-email?token= vs orval-generated POST). Coupled with the parked FUNCTIONAL_AUDIT §4#7 query→header migration; both should land together. Test rewrite: orval module mocked to delegate back to the existing apiClient mock. The 17 existing assertions on `expect(apiClient.post).toHaveBeenCalledWith('/auth/...', ...)` keep working without rewriting the test bodies, same shim pattern as B4 / B5. Tests: 302/302 in features/auth/ green. npm run typecheck: clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../auth/services/authService.test.ts | 26 ++++++++++ .../src/features/auth/services/authService.ts | 52 ++++++++++++++++--- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/apps/web/src/features/auth/services/authService.test.ts b/apps/web/src/features/auth/services/authService.test.ts index a18a844fe..f0ef30f45 100644 --- a/apps/web/src/features/auth/services/authService.test.ts +++ b/apps/web/src/features/auth/services/authService.test.ts @@ -33,6 +33,32 @@ const mockedApiClient = apiClient as { get: ReturnType; }; +// v1.0.8 B6 — login / logout / resendVerificationEmail / +// checkUsernameAvailability migrated to orval. Tests still mock +// `apiClient.post/get` for legacy ergonomics; the orval module's +// functions are stubbed to delegate back to those mocks so existing +// `toHaveBeenCalledWith('/auth/...', ...)` assertions keep working. +vi.mock('@/services/generated/auth/auth', () => ({ + postAuthLogin: vi.fn(async (body: unknown) => + mockedApiClient.post('/auth/login', body).then((r: { data?: unknown }) => r?.data), + ), + postAuthLogout: vi.fn(async () => + mockedApiClient.post('/auth/logout').then((r: { data?: unknown }) => r?.data), + ), + postAuthResendVerification: vi.fn(async (body: unknown) => + mockedApiClient + .post('/auth/resend-verification', body) + .then((r: { data?: unknown }) => r?.data), + ), + getAuthCheckUsername: vi.fn(async (params: { username: string }) => + mockedApiClient + .get( + `/auth/check-username?username=${encodeURIComponent(params.username)}`, + ) + .then((r: { data?: unknown }) => r?.data), + ), +})); + describe('authService', () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/apps/web/src/features/auth/services/authService.ts b/apps/web/src/features/auth/services/authService.ts index 1a0a6c474..c16bdbf6d 100644 --- a/apps/web/src/features/auth/services/authService.ts +++ b/apps/web/src/features/auth/services/authService.ts @@ -1,4 +1,32 @@ +/** + * Auth service — orval-backed (partial v1.0.8 B6). + * + * Migrated to orval generated client: + * login, logout, resendVerificationEmail, checkUsernameAvailability. + * + * Still on raw apiClient (deferred v1.0.9 — backend annotation gaps or + * field-name drift would risk breaking auth): + * - register → backend DTO says `password_confirmation` while the + * frontend currently sends `password_confirm` + * (register_request.go:8). Renaming via orval would + * change wire shape; needs explicit verification on + * prod first. + * - refreshToken → same pattern, backend expects `refresh_token` but + * current frontend sends `refreshToken`. + * - requestPasswordReset / resetPassword → endpoints not yet annotated + * in swaggo (no /auth/password/* in openapi.yaml). + * - verifyEmail → frontend uses GET /auth/verify-email?token= but + * orval-generated spec says POST. Verb drift + the + * query→header migration parked in FUNCTIONAL_AUDIT + * §4#7 should land together in v1.0.9. + */ import { apiClient } from '@/services/api/client'; +import { + postAuthLogin, + postAuthLogout, + postAuthResendVerification, + getAuthCheckUsername, +} from '@/services/generated/auth/auth'; import { handleApiServiceError } from '@/utils/serviceErrorHandler'; import type { LoginFormData, @@ -32,8 +60,10 @@ export interface AuthResponse { // INT-API-003: Standardized error handling using handleApiServiceError export async function login(data: LoginFormData): Promise { try { - const response = await apiClient.post('/auth/login', data); - return response.data; + const response = await postAuthLogin( + data as Parameters[0], + ); + return response as unknown as AuthResponse; } catch (error) { handleApiServiceError(error, { context: 'auth', @@ -79,7 +109,9 @@ export async function register(data: RegisterFormData): Promise { // INT-API-003: Standardized error handling using handleApiServiceError export async function logout(): Promise { try { - await apiClient.post('/auth/logout'); + // PostAuthLogoutBody.refresh_token is optional; the cookie-based session + // tear-down doesn't need a body. + await postAuthLogout({}); } catch (error) { handleApiServiceError(error, { context: 'auth' }); } @@ -162,7 +194,7 @@ export async function verifyEmail(token: string): Promise { // INT-API-003: Standardized error handling using handleApiServiceError export async function resendVerificationEmail(email: string): Promise { try { - await apiClient.post('/auth/resend-verification', { email }); + await postAuthResendVerification({ email }); } catch (error) { handleApiServiceError(error, { context: 'auth' }); } @@ -179,10 +211,14 @@ export async function checkUsernameAvailability( username: string, ): Promise { try { - const response = await apiClient.get<{ available: boolean }>( - `/auth/check-username?username=${encodeURIComponent(username)}`, - ); - return response.data.available; + const response = await getAuthCheckUsername({ username }); + const payload = response as unknown as + | { available: boolean } + | { data?: { available?: boolean } }; + if ('available' in payload) { + return payload.available; + } + return payload.data?.available ?? false; } catch (error) { handleApiServiceError(error, { context: 'auth' }); }