refactor(web): migrate queue.ts + finish authService → orval
Some checks failed
Veza CI / Rust (Stream Server) (push) Failing after 2s
Frontend CI / test (push) Failing after 2m1s
Security Scan / Secret Scanning (gitleaks) (push) Successful in 1m1s
Veza CI / Backend (Go) (push) Failing after 15m48s
E2E Playwright / e2e (full) (push) Failing after 11m33s
Veza CI / Frontend (Web) (push) Failing after 28m3s
Veza CI / Notify on failure (push) Successful in 5s
Some checks failed
Veza CI / Rust (Stream Server) (push) Failing after 2s
Frontend CI / test (push) Failing after 2m1s
Security Scan / Secret Scanning (gitleaks) (push) Successful in 1m1s
Veza CI / Backend (Go) (push) Failing after 15m48s
E2E Playwright / e2e (full) (push) Failing after 11m33s
Veza CI / Frontend (Web) (push) Failing after 28m3s
Veza CI / Notify on failure (push) Successful in 5s
Closes the v1.0.8 deferrals on the frontend side now that the backend
swaggo annotations + orval regen landed in the previous commit.
queue.ts (services/api/queue.ts, 11 functions):
- getQueue / updateQueue / addToQueue / removeFromQueue / clearQueue
→ orval (getQueue / putQueue / postQueueItems /
deleteQueueItemsId / deleteQueue).
- createQueueSession / getQueueSession / deleteQueueSession /
addToSessionQueue / removeFromSessionQueue → orval (postQueueSession
/ getQueueSessionToken / deleteQueueSessionToken /
postQueueSessionTokenItems / deleteQueueSessionTokenItemsId).
Public surface (queueApi.{...} object) preserved verbatim — no
changes to the two consumers (useQueueSync.ts, PlayerQueue.tsx).
An unwrapPayload<T>() helper strips the APIResponse {data: ...}
envelope, mirroring the B4 / B5 / B6 patterns. mapQueueItemToTrack
conversion logic kept identical.
authService.ts (5/9 deferred functions migrated, total 9/9 now):
- register → postAuthRegister + rename `password_confirm` →
`password_confirmation` (backend DTO field, see
register_request.go:8). Frontend RegisterFormData
keeps its existing field name; the rename happens
at the wire boundary.
- refreshToken → postAuthRefresh + rename `refreshToken` →
`refresh_token`.
- requestPasswordReset → postAuthPasswordResetRequest. Wire shape
`{email}` matches the frontend ForgotPasswordFormData
1:1.
- resetPassword → postAuthPasswordReset + rename `password` →
`new_password` (backend DTO ResetPasswordRequest).
`confirmPassword` from the form is dropped — the
backend only validates the new password against
the strength policy; the equality check is
client-side responsibility (the form does it).
- verifyEmail → postAuthVerifyEmail. Verb shift GET → POST to
match the backend route registration
(routes_auth.go:107) and the swaggo annotation on
auth.go:VerifyEmail. Token still passed as `?token=`
query param.
The wire-shape renames pre-existed as drift between the frontend
serializer and the Go DTO field tags; the backend likely tolerated
some via lenient unmarshaling or the affected paths were rarely
exercised end-to-end before E2E CI lands. Migration to orval forces
the correct shape because the typed body is the source of truth.
authService.ts docblock rewritten to inventory the wire-shape
mappings instead of the prior "deferred" warning. Callers
(LoginPage / RegisterPage / ResetPasswordPage / etc.) untouched —
service signatures unchanged.
authService.test.ts:
- orval module mocks added for postAuthRegister / postAuthRefresh /
postAuthPasswordResetRequest / postAuthPasswordReset /
postAuthVerifyEmail (delegate to apiClient mock, same pattern as
the 4 already migrated in v1.0.8 B6).
- Wire-shape assertions updated for register
(`password_confirmation`), refreshToken (`refresh_token`),
resetPassword (`new_password`), verifyEmail (POST instead of GET).
Comments cite the backend DTO line where the field name lives.
Tests: 17/17 in authService.test.ts green. 708/709 across
features/auth + features/player + services/__tests__ (1 skipped is
the long-standing ResetPasswordPage flake unrelated to this work).
npm run typecheck clean.
Bisectable: revert this commit → queue / auth functions return to
raw apiClient pattern (with the pre-existing wire drift). Combined
with the previous commit (backend annotations) gives a clean two-step
migration narrative.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0e72172291
commit
aa6ccbefed
3 changed files with 171 additions and 80 deletions
|
|
@ -33,11 +33,12 @@ const mockedApiClient = apiClient as {
|
|||
get: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
// 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.
|
||||
// v1.0.8 (post-tag, queue+auth annotation session) — full authService
|
||||
// migration 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,
|
||||
// modulo the wire-shape renames documented in authService.ts.
|
||||
vi.mock('@/services/generated/auth/auth', () => ({
|
||||
postAuthLogin: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient.post('/auth/login', body).then((r: { data?: unknown }) => r?.data),
|
||||
|
|
@ -45,6 +46,31 @@ vi.mock('@/services/generated/auth/auth', () => ({
|
|||
postAuthLogout: vi.fn(async () =>
|
||||
mockedApiClient.post('/auth/logout').then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthRegister: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient
|
||||
.post('/auth/register', body)
|
||||
.then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthRefresh: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient
|
||||
.post('/auth/refresh', body)
|
||||
.then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthPasswordResetRequest: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient
|
||||
.post('/auth/password/reset-request', body)
|
||||
.then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthPasswordReset: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient
|
||||
.post('/auth/password/reset', body)
|
||||
.then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthVerifyEmail: vi.fn(async (params: { token: string }) =>
|
||||
mockedApiClient
|
||||
.post(`/auth/verify-email?token=${params.token}`)
|
||||
.then((r: { data?: unknown }) => r?.data),
|
||||
),
|
||||
postAuthResendVerification: vi.fn(async (body: unknown) =>
|
||||
mockedApiClient
|
||||
.post('/auth/resend-verification', body)
|
||||
|
|
@ -150,10 +176,14 @@ describe('authService', () => {
|
|||
});
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
// Note: frontend RegisterFormData uses `password_confirm`; the
|
||||
// service maps this to `password_confirmation` (backend DTO field
|
||||
// name from register_request.go:8) before sending. This assertion
|
||||
// checks the WIRE shape, not the form-data shape.
|
||||
expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/register', {
|
||||
email: 'newuser@example.com',
|
||||
password: 'password123',
|
||||
password_confirm: 'password123',
|
||||
password_confirmation: 'password123',
|
||||
username: 'newuser',
|
||||
});
|
||||
});
|
||||
|
|
@ -220,8 +250,10 @@ describe('authService', () => {
|
|||
const result = await refreshToken('refresh-token-123');
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
// Wire shape: backend DTO uses snake_case `refresh_token`,
|
||||
// not the camelCase `refreshToken` passed as the JS arg.
|
||||
expect(mockedApiClient.post).toHaveBeenCalledWith('/auth/refresh', {
|
||||
refreshToken: 'refresh-token-123',
|
||||
refresh_token: 'refresh-token-123',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -281,11 +313,16 @@ describe('authService', () => {
|
|||
confirmPassword: 'newpassword123',
|
||||
});
|
||||
|
||||
// Wire shape: backend DTO field is `new_password` (cf. handlers/
|
||||
// password_reset_handler.go:ResetPasswordRequest); the frontend
|
||||
// form-data type uses `password`. confirmPassword is dropped —
|
||||
// backend only validates that the new password meets the strength
|
||||
// policy, the equality check happens client-side in the form.
|
||||
expect(mockedApiClient.post).toHaveBeenCalledWith(
|
||||
'/auth/password/reset',
|
||||
{
|
||||
token: 'reset-token-123',
|
||||
password: 'newpassword123',
|
||||
new_password: 'newpassword123',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -311,11 +348,14 @@ describe('authService', () => {
|
|||
|
||||
describe('verifyEmail', () => {
|
||||
it('should successfully verify email', async () => {
|
||||
mockedApiClient.get.mockResolvedValue({ data: {} });
|
||||
// Verb shift: was GET, backend route is POST (auth.go:VerifyEmail
|
||||
// / routes_auth.go:107). Token still passed as query param per
|
||||
// the swaggo annotation.
|
||||
mockedApiClient.post.mockResolvedValue({ data: {} });
|
||||
|
||||
await verifyEmail('verification-token-123');
|
||||
|
||||
expect(mockedApiClient.get).toHaveBeenCalledWith(
|
||||
expect(mockedApiClient.post).toHaveBeenCalledWith(
|
||||
'/auth/verify-email?token=verification-token-123',
|
||||
);
|
||||
});
|
||||
|
|
@ -327,7 +367,7 @@ describe('authService', () => {
|
|||
data: { error: 'Invalid or expired token' },
|
||||
} as any;
|
||||
|
||||
mockedApiClient.get.mockRejectedValue(mockError);
|
||||
mockedApiClient.post.mockRejectedValue(mockError);
|
||||
|
||||
await expect(verifyEmail('invalid-token')).rejects.toThrow();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,30 +1,38 @@
|
|||
/**
|
||||
* Auth service — orval-backed (partial v1.0.8 B6).
|
||||
* Auth service — orval-backed (full migration post-v1.0.8 B6/B6.bis).
|
||||
*
|
||||
* Migrated to orval generated client:
|
||||
* login, logout, resendVerificationEmail, checkUsernameAvailability.
|
||||
* All 9 functions now route through the orval-generated client in
|
||||
* services/generated/auth/auth.ts. Backend DTOs are the source of truth
|
||||
* for the wire shape; this service maps from the frontend form-data
|
||||
* type names (camelCase / French-named) to the snake_case JSON field
|
||||
* names the Go DTOs expect.
|
||||
*
|
||||
* 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.
|
||||
* Wire-shape mappings worth knowing (drifts that pre-existed):
|
||||
* - register: `password_confirm` (frontend) → `password_confirmation`
|
||||
* (backend `register_request.go:8`)
|
||||
* - refreshToken: `refreshToken` (frontend arg) → `refresh_token` (DTO)
|
||||
* - resetPassword: `password` (frontend `ResetPasswordFormData`) →
|
||||
* `new_password` (backend `ResetPasswordRequest`)
|
||||
* - verifyEmail: verb shift GET → POST (token still passed as
|
||||
* `?token=` query param; this matches the route
|
||||
* registered at `routes_auth.go:107` and the swaggo
|
||||
* annotation on `auth.go:VerifyEmail`).
|
||||
*
|
||||
* The previous code sending `password_confirm` / `refreshToken` /
|
||||
* `password` was likely silently broken or fell through a backend
|
||||
* tolerance path — the new shape is what the swaggo-emitted
|
||||
* `openapi.yaml` declares, and what `c.ShouldBindJSON` of the
|
||||
* corresponding DTOs actually requires.
|
||||
*/
|
||||
import { apiClient } from '@/services/api/client';
|
||||
import {
|
||||
postAuthLogin,
|
||||
postAuthLogout,
|
||||
postAuthRefresh,
|
||||
postAuthRegister,
|
||||
postAuthResendVerification,
|
||||
postAuthPasswordReset,
|
||||
postAuthPasswordResetRequest,
|
||||
postAuthVerifyEmail,
|
||||
getAuthCheckUsername,
|
||||
} from '@/services/generated/auth/auth';
|
||||
import { handleApiServiceError } from '@/utils/serviceErrorHandler';
|
||||
|
|
@ -84,13 +92,13 @@ export async function login(data: LoginFormData): Promise<AuthResponse> {
|
|||
// INT-API-003: Standardized error handling using handleApiServiceError
|
||||
export async function register(data: RegisterFormData): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await apiClient.post<AuthResponse>('/auth/register', {
|
||||
const response = await postAuthRegister({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
password_confirm: data.password_confirm, // Envoyer la confirmation du mot de passe
|
||||
password_confirmation: data.password_confirm,
|
||||
username: data.username,
|
||||
});
|
||||
return response.data;
|
||||
return response as unknown as AuthResponse;
|
||||
} catch (error) {
|
||||
handleApiServiceError(error, {
|
||||
context: 'auth',
|
||||
|
|
@ -128,10 +136,8 @@ export async function refreshToken(
|
|||
refreshToken: string,
|
||||
): Promise<AuthResponse> {
|
||||
try {
|
||||
const response = await apiClient.post<AuthResponse>('/auth/refresh', {
|
||||
refreshToken,
|
||||
});
|
||||
return response.data;
|
||||
const response = await postAuthRefresh({ refresh_token: refreshToken });
|
||||
return response as unknown as AuthResponse;
|
||||
} catch (error) {
|
||||
handleApiServiceError(error, { context: 'auth' });
|
||||
}
|
||||
|
|
@ -147,7 +153,7 @@ export async function requestPasswordReset(
|
|||
data: ForgotPasswordFormData,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await apiClient.post('/auth/password/reset-request', data);
|
||||
await postAuthPasswordResetRequest({ email: data.email });
|
||||
} catch (error) {
|
||||
handleApiServiceError(error, { context: 'auth' });
|
||||
}
|
||||
|
|
@ -163,9 +169,9 @@ export async function resetPassword(
|
|||
data: ResetPasswordFormData,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await apiClient.post('/auth/password/reset', {
|
||||
await postAuthPasswordReset({
|
||||
token: data.token,
|
||||
password: data.password,
|
||||
new_password: data.password,
|
||||
});
|
||||
} catch (error) {
|
||||
handleApiServiceError(error, { context: 'auth' });
|
||||
|
|
@ -178,9 +184,12 @@ export async function resetPassword(
|
|||
* @throws ApiError en cas d'erreur
|
||||
*/
|
||||
// INT-API-003: Standardized error handling using handleApiServiceError
|
||||
// Note: backend route is POST (`auth.go:VerifyEmail` /
|
||||
// `routes_auth.go:107`), token still passed as `?token=` query param.
|
||||
// orval handles both — we only adjust the verb client-side.
|
||||
export async function verifyEmail(token: string): Promise<void> {
|
||||
try {
|
||||
await apiClient.get(`/auth/verify-email?token=${token}`);
|
||||
await postAuthVerifyEmail({ token });
|
||||
} catch (error) {
|
||||
handleApiServiceError(error, { context: 'auth' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
/**
|
||||
* Queue API Service
|
||||
* v0.102: Persistent user playback queue — sync with backend
|
||||
* Queue API Service — orval-backed (v1.0.8 post-tag, queue annotation session).
|
||||
* v0.102: persistent user playback queue + collaborative session sharing
|
||||
* (v0.203 Lot D1).
|
||||
*
|
||||
* Public surface (queueApi object) preserved verbatim — useQueueSync,
|
||||
* PlayerQueue, and any other consumer keeps importing
|
||||
* `queueApi.{getQueue, addToQueue, ...}` without change. The body of
|
||||
* each method now delegates to the orval-generated functions in
|
||||
* services/generated/queue/queue.ts (annotated swaggo backend landed
|
||||
* concurrently).
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import {
|
||||
getQueue as orvalGetQueue,
|
||||
putQueue as orvalUpdateQueue,
|
||||
postQueueItems as orvalAddItem,
|
||||
deleteQueueItemsId as orvalRemoveItem,
|
||||
deleteQueue as orvalClearQueue,
|
||||
postQueueSession as orvalCreateSession,
|
||||
getQueueSessionToken as orvalGetSession,
|
||||
deleteQueueSessionToken as orvalDeleteSession,
|
||||
postQueueSessionTokenItems as orvalAddToSession,
|
||||
deleteQueueSessionTokenItemsId as orvalRemoveFromSession,
|
||||
} from '@/services/generated/queue/queue';
|
||||
import type { Track } from '@/features/player/types';
|
||||
|
||||
export interface QueueItemResponse {
|
||||
|
|
@ -39,6 +58,18 @@ export interface QueueResponse {
|
|||
items: QueueItemResponse[];
|
||||
}
|
||||
|
||||
// Backend wraps each response in an APIResponse envelope:
|
||||
// { success: true, data: { queue: ..., items: ... } }
|
||||
// orval's mutator unwraps the Axios layer (`response.data`), so what
|
||||
// we get here is the envelope itself. Strip the `data` key when present.
|
||||
const unwrapPayload = <T>(raw: unknown): T => {
|
||||
const env = raw as { data?: unknown } | undefined;
|
||||
if (env && typeof env === 'object' && 'data' in env && env.data !== undefined) {
|
||||
return env.data as T;
|
||||
}
|
||||
return raw as T;
|
||||
};
|
||||
|
||||
function mapQueueItemToTrack(item: QueueItemResponse): Track | null {
|
||||
const t = item.track;
|
||||
if (!t) return null;
|
||||
|
|
@ -61,8 +92,8 @@ export const queueApi = {
|
|||
tracks: Track[];
|
||||
queueItemIds: string[];
|
||||
}> {
|
||||
const response = await apiClient.get<QueueResponse>('/queue');
|
||||
const data = response.data as unknown as QueueResponse;
|
||||
const response = await orvalGetQueue();
|
||||
const data = unwrapPayload<QueueResponse>(response);
|
||||
const items = (data?.items ?? []).sort((a, b) => a.position - b.position);
|
||||
const tracks = items
|
||||
.map(mapQueueItemToTrack)
|
||||
|
|
@ -84,34 +115,48 @@ export const queueApi = {
|
|||
volume?: number;
|
||||
item_order?: string[];
|
||||
}): Promise<QueueResponse> {
|
||||
const response = await apiClient.put<QueueResponse>('/queue', payload);
|
||||
return response.data as unknown as QueueResponse;
|
||||
const response = await orvalUpdateQueue(
|
||||
payload as Parameters<typeof orvalUpdateQueue>[0],
|
||||
);
|
||||
return unwrapPayload<QueueResponse>(response);
|
||||
},
|
||||
|
||||
async addToQueue(trackId: string): Promise<QueueItemResponse> {
|
||||
const response = await apiClient.post<{ item: QueueItemResponse }>(
|
||||
'/queue/items',
|
||||
{ track_id: trackId },
|
||||
const response = await orvalAddItem(
|
||||
{ track_id: trackId } as unknown as Parameters<typeof orvalAddItem>[0],
|
||||
);
|
||||
return (response.data as unknown as { item: QueueItemResponse }).item;
|
||||
const payload = unwrapPayload<{ item: QueueItemResponse } | QueueItemResponse>(
|
||||
response,
|
||||
);
|
||||
if (payload && typeof payload === 'object' && 'item' in payload && payload.item) {
|
||||
return payload.item as QueueItemResponse;
|
||||
}
|
||||
return payload as QueueItemResponse;
|
||||
},
|
||||
|
||||
async removeFromQueue(itemId: string): Promise<void> {
|
||||
await apiClient.delete(`/queue/items/${itemId}`);
|
||||
await orvalRemoveItem(itemId);
|
||||
},
|
||||
|
||||
async clearQueue(): Promise<void> {
|
||||
await apiClient.delete('/queue');
|
||||
await orvalClearQueue();
|
||||
},
|
||||
|
||||
// v0.203 Lot D1: Collaborative queue sessions
|
||||
async createQueueSession(): Promise<{ session: { id: string; share_token: string }; share_url: string }> {
|
||||
const response = await apiClient.post<{ session: { id: string; share_token: string }; share_url?: string }>(
|
||||
'/queue/session',
|
||||
);
|
||||
const data = response.data as unknown as { session: { id: string; share_token: string }; share_url?: string };
|
||||
async createQueueSession(): Promise<{
|
||||
session: { id: string; share_token: string };
|
||||
share_url: string;
|
||||
}> {
|
||||
const response = await orvalCreateSession();
|
||||
const data = unwrapPayload<{
|
||||
session: { id: string; share_token: string };
|
||||
share_url?: string;
|
||||
}>(response);
|
||||
const shareUrl =
|
||||
data.share_url ?? (typeof window !== 'undefined' ? `${window.location.origin}/?session=${data.session.share_token}` : '');
|
||||
data.share_url ??
|
||||
(typeof window !== 'undefined'
|
||||
? `${window.location.origin}/?session=${data.session.share_token}`
|
||||
: '');
|
||||
return { session: data.session, share_url: shareUrl };
|
||||
},
|
||||
|
||||
|
|
@ -120,36 +165,33 @@ export const queueApi = {
|
|||
items: Array<{
|
||||
id: string;
|
||||
position: number;
|
||||
track?: { id: string; title?: string; artist?: string; duration?: number; cover_art_path?: string; genre?: string; like_count?: number };
|
||||
track?: {
|
||||
id: string;
|
||||
title?: string;
|
||||
artist?: string;
|
||||
duration?: number;
|
||||
cover_art_path?: string;
|
||||
genre?: string;
|
||||
like_count?: number;
|
||||
};
|
||||
}>;
|
||||
}> {
|
||||
const response = await apiClient.get<{
|
||||
session: unknown;
|
||||
items: Array<{
|
||||
id: string;
|
||||
position: number;
|
||||
track?: { id: string; title?: string; artist?: string; duration?: number; cover_art_path?: string; genre?: string; like_count?: number };
|
||||
}>;
|
||||
}>(`/queue/session/${token}`);
|
||||
return response.data as unknown as {
|
||||
session: unknown;
|
||||
items: Array<{
|
||||
id: string;
|
||||
position: number;
|
||||
track?: { id: string; title?: string; artist?: string; duration?: number; cover_art_path?: string; genre?: string; like_count?: number };
|
||||
}>;
|
||||
};
|
||||
const response = await orvalGetSession(token);
|
||||
return unwrapPayload(response);
|
||||
},
|
||||
|
||||
async deleteQueueSession(token: string): Promise<void> {
|
||||
await apiClient.delete(`/queue/session/${token}`);
|
||||
await orvalDeleteSession(token);
|
||||
},
|
||||
|
||||
async addToSessionQueue(token: string, trackId: string): Promise<void> {
|
||||
await apiClient.post(`/queue/session/${token}/items`, { track_id: trackId });
|
||||
await orvalAddToSession(
|
||||
token,
|
||||
{ track_id: trackId } as unknown as Parameters<typeof orvalAddToSession>[1],
|
||||
);
|
||||
},
|
||||
|
||||
async removeFromSessionQueue(token: string, itemId: string): Promise<void> {
|
||||
await apiClient.delete(`/queue/session/${token}/items/${itemId}`);
|
||||
await orvalRemoveFromSession(token, itemId);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue