You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
4.7 KiB
TypeScript
160 lines
4.7 KiB
TypeScript
import { BookingError } from '@/src/lib/errors';
|
|
import { BookingResponseOk } from '@/src/lib/types';
|
|
|
|
type ApiClient = (url: string, options?: RequestInit) => Promise<Response>;
|
|
|
|
/**
|
|
* Service layer for booking-related API operations
|
|
* Centralizes all API calls and error handling
|
|
*/
|
|
export class BookingService {
|
|
constructor(private readonly apiClient: ApiClient) {}
|
|
|
|
/**
|
|
* Get booking details by slot ID
|
|
*/
|
|
async getBooking(slotId: string): Promise<BookingResponseOk> {
|
|
try {
|
|
const response = await this.apiClient(`/booking/${encodeURIComponent(slotId)}`);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) {
|
|
throw BookingError.notFound(slotId);
|
|
}
|
|
|
|
const data = await response.json().catch(() => ({ message: 'Failed to load booking' }));
|
|
throw BookingError.serverError(data.message || 'Failed to load booking');
|
|
}
|
|
|
|
return response.json();
|
|
} catch (error) {
|
|
if (error instanceof BookingError) throw error;
|
|
throw BookingError.networkError('Network error while loading booking');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Approve a swap approval request
|
|
*/
|
|
async approveSwap(slotId: string, approvalId: number): Promise<void> {
|
|
try {
|
|
const response = await this.apiClient(
|
|
`/booking/${slotId}/swap-approval/${approvalId}`,
|
|
{ method: 'PATCH' }
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json().catch(() => ({ message: 'Failed to approve swap' }));
|
|
|
|
if (response.status === 400) {
|
|
throw BookingError.badRequest(data.message || 'Invalid approval request');
|
|
}
|
|
|
|
throw BookingError.serverError(data.message || 'Failed to approve swap');
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof BookingError) throw error;
|
|
throw BookingError.networkError('Network error while approving swap');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancel a swap group
|
|
*/
|
|
async cancelSwap(slotId: string, swapGroupId: string): Promise<void> {
|
|
try {
|
|
const response = await this.apiClient(
|
|
`/booking/${slotId}/swap-group/${swapGroupId}`,
|
|
{ method: 'DELETE' }
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json().catch(() => ({ message: 'Failed to cancel swap' }));
|
|
|
|
if (response.status === 403) {
|
|
throw BookingError.forbidden(data.message || 'Only the initiator can cancel a swap');
|
|
}
|
|
|
|
if (response.status === 400) {
|
|
throw BookingError.badRequest(data.message || 'Invalid cancellation request');
|
|
}
|
|
|
|
throw BookingError.serverError(data.message || 'Failed to cancel swap');
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof BookingError) throw error;
|
|
throw BookingError.networkError('Network error while cancelling swap');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submit or update match score
|
|
*/
|
|
async submitMatchScore(
|
|
slotId: string,
|
|
sets: number[][],
|
|
method: 'POST' | 'PATCH' = 'POST'
|
|
): Promise<void> {
|
|
try {
|
|
const response = await this.apiClient(
|
|
`/booking/${slotId}/match/result`,
|
|
{
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ sets })
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json().catch(() => ({ message: 'Failed to submit score' }));
|
|
|
|
if (response.status === 400) {
|
|
throw BookingError.badRequest(data.message || 'Invalid score data');
|
|
}
|
|
|
|
throw BookingError.serverError(data.message || 'Failed to submit score');
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof BookingError) throw error;
|
|
throw BookingError.networkError('Network error while submitting score');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change match type
|
|
*/
|
|
async changeMatchType(
|
|
slotId: string,
|
|
matchTypeId: number,
|
|
forced: boolean = false
|
|
): Promise<void> {
|
|
try {
|
|
const response = await this.apiClient(
|
|
`/booking/${slotId}/match-type`,
|
|
{
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ match_type_id: matchTypeId, forced })
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json().catch(() => ({ message: 'Failed to change match type' }));
|
|
|
|
if (response.status === 400) {
|
|
throw BookingError.badRequest(data.message || 'Invalid match type change request');
|
|
}
|
|
|
|
if (response.status === 403) {
|
|
throw BookingError.forbidden(data.message || 'Not authorized to change match type');
|
|
}
|
|
|
|
throw BookingError.serverError(data.message || 'Failed to change match type');
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof BookingError) throw error;
|
|
throw BookingError.networkError('Network error while changing match type');
|
|
}
|
|
}
|
|
}
|