refactor(web): migrate dashboard service to orval client (v1.0.8 P1 pilote)
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
Pivoted B2 pilote from developer.ts → dashboard because the developer
endpoints (/developer/api-keys) are not yet covered by swaggo annotations
in veza-backend-api, so they do not appear in openapi.yaml. Completing
the OpenAPI spec is a backend chantier of its own (v1.0.9 scope).
Dashboard was chosen instead:
- single endpoint (GET /api/v1/dashboard)
- fully spec-covered (Dashboard tag)
- non-trivial consumer chain (feature/dashboard/services → hooks → UI)
Changes:
- apps/web/src/features/dashboard/services/dashboardService.ts
Replace `apiClient.get('/dashboard', { params, signal })` with
`getApiV1Dashboard({ activity_limit, library_limit, stats_period },
{ signal })`. Same response shape, same error fallback, same
interceptor chain — only the fetch call is now typed + generated.
Removes the direct @/services/api/client import.
- apps/web/src/services/api/orval-mutator.ts
New `stripBaseURLPrefix` helper. Orval emits absolute paths
(e.g. `/api/v1/dashboard`) but apiClient.baseURL resolves to
`/api/v1` already. The mutator now strips a matching `/api/vN`
prefix before delegating to apiClient, preventing double-prefix.
No-op when baseURL lacks the prefix.
Verification:
- npm run typecheck ✅
- npm run lint ✅ (0 errors, pre-existing warnings unchanged)
- npm test -- --run src/features/dashboard ✅ 4/4 pass
Scope adjustment (discovered during execution): many hand-written
services (developer, search, queue, social, metrics) call endpoints
that lack swaggo annotations. Full bulk migration (original B3-B8)
requires completing the OpenAPI spec first. Next direct-migration
candidates are the fully spec-covered services: auth, track, user,
playlist, marketplace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a170504784
commit
7fd43ab609
2 changed files with 41 additions and 17 deletions
|
|
@ -1,8 +1,13 @@
|
|||
import { apiClient } from '@/services/api/client';
|
||||
import { isCancel } from 'axios';
|
||||
|
||||
import { getApiV1Dashboard } from '@/services/generated/dashboard/dashboard';
|
||||
import { logger } from '@/utils/logger';
|
||||
|
||||
// BE-PAGE-001: Dashboard service for fetching dashboard data
|
||||
// v1.0.8 Phase 1 (B2 pilote) — migrated to orval-generated client.
|
||||
// Previously used apiClient.get('/dashboard', ...); now calls
|
||||
// getApiV1Dashboard() which routes through vezaMutator → apiClient,
|
||||
// keeping auth / CSRF / retry / offline-queue interceptors intact.
|
||||
|
||||
export interface DashboardStats {
|
||||
tracks_played: number;
|
||||
|
|
@ -70,22 +75,22 @@ export async function getDashboardData(
|
|||
signal?: AbortSignal,
|
||||
): Promise<DashboardData> {
|
||||
try {
|
||||
const params: Record<string, string | number> = {};
|
||||
if (options?.activityLimit) {
|
||||
params.activity_limit = options.activityLimit;
|
||||
}
|
||||
if (options?.libraryLimit) {
|
||||
params.library_limit = options.libraryLimit;
|
||||
}
|
||||
if (options?.statsPeriod) {
|
||||
params.stats_period = options.statsPeriod;
|
||||
}
|
||||
// v1.0.8 B2 pilote: orval-generated `getApiV1Dashboard` replaces
|
||||
// the raw `apiClient.get('/dashboard', ...)` call. Query params are
|
||||
// serialised automatically from the typed GetApiV1DashboardParams.
|
||||
const result = await getApiV1Dashboard(
|
||||
{
|
||||
activity_limit: options?.activityLimit,
|
||||
library_limit: options?.libraryLimit,
|
||||
stats_period: options?.statsPeriod,
|
||||
},
|
||||
signal ? { signal } : undefined,
|
||||
);
|
||||
|
||||
const response = await apiClient.get('/dashboard', { params, signal });
|
||||
|
||||
// Response is already unwrapped by API client interceptor
|
||||
// Expected format: { success: true, data: DashboardResponse }
|
||||
const dashboardData = response.data;
|
||||
// Orval returns the discriminated response envelope; the API client
|
||||
// response interceptor unwraps `data.data` so we receive the payload.
|
||||
// Cast to the runtime shape we validated on the legacy path.
|
||||
const dashboardData = (result as unknown as { stats?: DashboardStats; recent_activity?: RecentActivity[]; library_preview?: LibraryPreview });
|
||||
|
||||
if (!dashboardData) {
|
||||
throw new Error('Invalid dashboard response format');
|
||||
|
|
|
|||
|
|
@ -37,9 +37,28 @@ const toPlainHeaders = (
|
|||
return headers as Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip a leading /api/v1 (or /api/vN) prefix from the orval-generated URL
|
||||
* when the Axios baseURL already provides it. Backend swaggo annotations
|
||||
* emit absolute paths (`/api/v1/foo`) but apiClient.baseURL defaults to
|
||||
* `/api/v1` (cf. env.API_URL), so we'd otherwise double-prefix.
|
||||
*
|
||||
* If baseURL does not end in `/api/vN`, the URL is returned as-is.
|
||||
*/
|
||||
const stripBaseURLPrefix = (url: string): string => {
|
||||
const base = apiClient.defaults.baseURL ?? '';
|
||||
const match = base.match(/\/api\/v\d+$/);
|
||||
if (!match) return url;
|
||||
const prefix = match[0];
|
||||
if (url.startsWith(`${prefix}/`)) {
|
||||
return url.slice(prefix.length);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const vezaMutator = <T>(url: string, init?: RequestInit): Promise<T> => {
|
||||
const config: AxiosRequestConfig = {
|
||||
url,
|
||||
url: stripBaseURLPrefix(url),
|
||||
method: (init?.method ?? 'GET') as Method,
|
||||
headers: toPlainHeaders(init?.headers),
|
||||
// orval serialises bodies with JSON.stringify before calling the mutator;
|
||||
|
|
|
|||
Loading…
Reference in a new issue