mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 07:31:07 -05:00
- Restructure API v1 with improved serializers organization - Add user deletion requests and moderation queue system - Implement bulk moderation operations and permissions - Add user profile enhancements with display names and avatars - Expand ride and park API endpoints with better filtering - Add manufacturer API with detailed ride relationships - Improve authentication flows and error handling - Update frontend documentation and API specifications
730 lines
26 KiB
TypeScript
730 lines
26 KiB
TypeScript
// ThrillWiki API Client for NextJS Frontend
|
|
// Last updated: 2025-08-29
|
|
// This file contains the complete API client implementation for ThrillWiki
|
|
|
|
import {
|
|
ApiResponse,
|
|
PaginatedResponse,
|
|
// Moderation types
|
|
ModerationReport,
|
|
CreateModerationReportData,
|
|
UpdateModerationReportData,
|
|
ModerationQueue,
|
|
CompleteQueueItemData,
|
|
ModerationAction,
|
|
CreateModerationActionData,
|
|
BulkOperation,
|
|
CreateBulkOperationData,
|
|
UserModerationProfile,
|
|
ModerationStatsData,
|
|
// Filter types
|
|
ModerationReportFilters,
|
|
ModerationQueueFilters,
|
|
ModerationActionFilters,
|
|
BulkOperationFilters,
|
|
ParkFilters,
|
|
RideFilters,
|
|
SearchFilters,
|
|
// Entity types
|
|
Park,
|
|
Ride,
|
|
Manufacturer,
|
|
RideModel,
|
|
ParkPhoto,
|
|
RidePhoto,
|
|
RideReview,
|
|
CreateRideReviewData,
|
|
// Auth types
|
|
LoginData,
|
|
SignupData,
|
|
AuthResponse,
|
|
UserProfile,
|
|
PasswordResetData,
|
|
PasswordChangeData,
|
|
// Stats types
|
|
GlobalStats,
|
|
TrendingContent,
|
|
// Utility types
|
|
ApiClientConfig,
|
|
RequestConfig,
|
|
} from '@/types/api';
|
|
|
|
// ============================================================================
|
|
// API Client Configuration
|
|
// ============================================================================
|
|
|
|
const DEFAULT_CONFIG: ApiClientConfig = {
|
|
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1',
|
|
timeout: 30000,
|
|
retries: 3,
|
|
retryDelay: 1000,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// HTTP Client Class
|
|
// ============================================================================
|
|
|
|
class HttpClient {
|
|
private config: ApiClientConfig;
|
|
private authToken: string | null = null;
|
|
|
|
constructor(config: Partial<ApiClientConfig> = {}) {
|
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
}
|
|
|
|
setAuthToken(token: string | null) {
|
|
this.authToken = token;
|
|
}
|
|
|
|
private getHeaders(customHeaders: Record<string, string> = {}): Record<string, string> {
|
|
const headers = { ...this.config.headers, ...customHeaders };
|
|
|
|
if (this.authToken) {
|
|
headers.Authorization = `Bearer ${this.authToken}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
private async makeRequest<T>(config: RequestConfig): Promise<ApiResponse<T>> {
|
|
const url = `${this.config.baseURL}${config.url}`;
|
|
const headers = this.getHeaders(config.headers);
|
|
|
|
const requestConfig: RequestInit = {
|
|
method: config.method,
|
|
headers,
|
|
signal: AbortSignal.timeout(config.timeout || this.config.timeout),
|
|
};
|
|
|
|
if (config.data && ['POST', 'PUT', 'PATCH'].includes(config.method)) {
|
|
requestConfig.body = JSON.stringify(config.data);
|
|
}
|
|
|
|
// Add query parameters for GET requests
|
|
const finalUrl = config.params && config.method === 'GET'
|
|
? `${url}?${new URLSearchParams(config.params).toString()}`
|
|
: url;
|
|
|
|
try {
|
|
const response = await fetch(finalUrl, requestConfig);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
return {
|
|
status: 'error',
|
|
data: null,
|
|
error: data.error || {
|
|
code: 'HTTP_ERROR',
|
|
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
return data;
|
|
} catch (error) {
|
|
return {
|
|
status: 'error',
|
|
data: null,
|
|
error: {
|
|
code: 'NETWORK_ERROR',
|
|
message: error instanceof Error ? error.message : 'Network request failed',
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
async get<T>(url: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
|
return this.makeRequest<T>({ method: 'GET', url, params, headers });
|
|
}
|
|
|
|
async post<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
|
return this.makeRequest<T>({ method: 'POST', url, data, headers });
|
|
}
|
|
|
|
async put<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
|
return this.makeRequest<T>({ method: 'PUT', url, data, headers });
|
|
}
|
|
|
|
async patch<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
|
return this.makeRequest<T>({ method: 'PATCH', url, data, headers });
|
|
}
|
|
|
|
async delete<T>(url: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
|
|
return this.makeRequest<T>({ method: 'DELETE', url, headers });
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// API Client Class
|
|
// ============================================================================
|
|
|
|
export class ThrillWikiApiClient {
|
|
private http: HttpClient;
|
|
|
|
constructor(config?: Partial<ApiClientConfig>) {
|
|
this.http = new HttpClient(config);
|
|
}
|
|
|
|
setAuthToken(token: string | null) {
|
|
this.http.setAuthToken(token);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Authentication API
|
|
// ============================================================================
|
|
|
|
auth = {
|
|
login: async (data: LoginData): Promise<ApiResponse<AuthResponse>> => {
|
|
return this.http.post<AuthResponse>('/auth/login/', data);
|
|
},
|
|
|
|
signup: async (data: SignupData): Promise<ApiResponse<AuthResponse>> => {
|
|
return this.http.post<AuthResponse>('/auth/signup/', data);
|
|
},
|
|
|
|
logout: async (): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/auth/logout/');
|
|
},
|
|
|
|
getCurrentUser: async (): Promise<ApiResponse<UserProfile>> => {
|
|
return this.http.get<UserProfile>('/auth/user/');
|
|
},
|
|
|
|
resetPassword: async (data: PasswordResetData): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/auth/password/reset/', data);
|
|
},
|
|
|
|
changePassword: async (data: PasswordChangeData): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/auth/password/change/', data);
|
|
},
|
|
|
|
getAuthStatus: async (): Promise<ApiResponse<{ is_authenticated: boolean; user?: UserProfile }>> => {
|
|
return this.http.get('/auth/status/');
|
|
},
|
|
|
|
getSocialProviders: async (): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/auth/providers/');
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Moderation API
|
|
// ============================================================================
|
|
|
|
moderation = {
|
|
// Reports
|
|
reports: {
|
|
list: async (filters?: ModerationReportFilters): Promise<ApiResponse<PaginatedResponse<ModerationReport>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationReport>>('/moderation/reports/', filters);
|
|
},
|
|
|
|
create: async (data: CreateModerationReportData): Promise<ApiResponse<ModerationReport>> => {
|
|
return this.http.post<ModerationReport>('/moderation/reports/', data);
|
|
},
|
|
|
|
get: async (id: number): Promise<ApiResponse<ModerationReport>> => {
|
|
return this.http.get<ModerationReport>(`/moderation/reports/${id}/`);
|
|
},
|
|
|
|
update: async (id: number, data: Partial<UpdateModerationReportData>): Promise<ApiResponse<ModerationReport>> => {
|
|
return this.http.patch<ModerationReport>(`/moderation/reports/${id}/`, data);
|
|
},
|
|
|
|
delete: async (id: number): Promise<ApiResponse<null>> => {
|
|
return this.http.delete<null>(`/moderation/reports/${id}/`);
|
|
},
|
|
|
|
assign: async (id: number, moderatorId: number): Promise<ApiResponse<ModerationReport>> => {
|
|
return this.http.post<ModerationReport>(`/moderation/reports/${id}/assign/`, { moderator_id: moderatorId });
|
|
},
|
|
|
|
resolve: async (id: number, resolutionAction: string, resolutionNotes?: string): Promise<ApiResponse<ModerationReport>> => {
|
|
return this.http.post<ModerationReport>(`/moderation/reports/${id}/resolve/`, {
|
|
resolution_action: resolutionAction,
|
|
resolution_notes: resolutionNotes || '',
|
|
});
|
|
},
|
|
|
|
getStats: async (): Promise<ApiResponse<ModerationStatsData>> => {
|
|
return this.http.get<ModerationStatsData>('/moderation/reports/stats/');
|
|
},
|
|
},
|
|
|
|
// Queue
|
|
queue: {
|
|
list: async (filters?: ModerationQueueFilters): Promise<ApiResponse<PaginatedResponse<ModerationQueue>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationQueue>>('/moderation/queue/', filters);
|
|
},
|
|
|
|
create: async (data: Partial<ModerationQueue>): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.post<ModerationQueue>('/moderation/queue/', data);
|
|
},
|
|
|
|
get: async (id: number): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.get<ModerationQueue>(`/moderation/queue/${id}/`);
|
|
},
|
|
|
|
update: async (id: number, data: Partial<ModerationQueue>): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.patch<ModerationQueue>(`/moderation/queue/${id}/`, data);
|
|
},
|
|
|
|
delete: async (id: number): Promise<ApiResponse<null>> => {
|
|
return this.http.delete<null>(`/moderation/queue/${id}/`);
|
|
},
|
|
|
|
assign: async (id: number, moderatorId: number): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/assign/`, { moderator_id: moderatorId });
|
|
},
|
|
|
|
unassign: async (id: number): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/unassign/`);
|
|
},
|
|
|
|
complete: async (id: number, data: CompleteQueueItemData): Promise<ApiResponse<ModerationQueue>> => {
|
|
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/complete/`, data);
|
|
},
|
|
|
|
getMyQueue: async (): Promise<ApiResponse<PaginatedResponse<ModerationQueue>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationQueue>>('/moderation/queue/my_queue/');
|
|
},
|
|
},
|
|
|
|
// Actions
|
|
actions: {
|
|
list: async (filters?: ModerationActionFilters): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/', filters);
|
|
},
|
|
|
|
create: async (data: CreateModerationActionData): Promise<ApiResponse<ModerationAction>> => {
|
|
return this.http.post<ModerationAction>('/moderation/actions/', data);
|
|
},
|
|
|
|
get: async (id: number): Promise<ApiResponse<ModerationAction>> => {
|
|
return this.http.get<ModerationAction>(`/moderation/actions/${id}/`);
|
|
},
|
|
|
|
update: async (id: number, data: Partial<ModerationAction>): Promise<ApiResponse<ModerationAction>> => {
|
|
return this.http.patch<ModerationAction>(`/moderation/actions/${id}/`, data);
|
|
},
|
|
|
|
delete: async (id: number): Promise<ApiResponse<null>> => {
|
|
return this.http.delete<null>(`/moderation/actions/${id}/`);
|
|
},
|
|
|
|
deactivate: async (id: number): Promise<ApiResponse<ModerationAction>> => {
|
|
return this.http.post<ModerationAction>(`/moderation/actions/${id}/deactivate/`);
|
|
},
|
|
|
|
getActive: async (): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/active/');
|
|
},
|
|
|
|
getExpired: async (): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
|
|
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/expired/');
|
|
},
|
|
},
|
|
|
|
// Bulk Operations
|
|
bulkOperations: {
|
|
list: async (filters?: BulkOperationFilters): Promise<ApiResponse<PaginatedResponse<BulkOperation>>> => {
|
|
return this.http.get<PaginatedResponse<BulkOperation>>('/moderation/bulk-operations/', filters);
|
|
},
|
|
|
|
create: async (data: CreateBulkOperationData): Promise<ApiResponse<BulkOperation>> => {
|
|
return this.http.post<BulkOperation>('/moderation/bulk-operations/', data);
|
|
},
|
|
|
|
get: async (id: string): Promise<ApiResponse<BulkOperation>> => {
|
|
return this.http.get<BulkOperation>(`/moderation/bulk-operations/${id}/`);
|
|
},
|
|
|
|
update: async (id: string, data: Partial<BulkOperation>): Promise<ApiResponse<BulkOperation>> => {
|
|
return this.http.patch<BulkOperation>(`/moderation/bulk-operations/${id}/`, data);
|
|
},
|
|
|
|
delete: async (id: string): Promise<ApiResponse<null>> => {
|
|
return this.http.delete<null>(`/moderation/bulk-operations/${id}/`);
|
|
},
|
|
|
|
cancel: async (id: string): Promise<ApiResponse<BulkOperation>> => {
|
|
return this.http.post<BulkOperation>(`/moderation/bulk-operations/${id}/cancel/`);
|
|
},
|
|
|
|
retry: async (id: string): Promise<ApiResponse<BulkOperation>> => {
|
|
return this.http.post<BulkOperation>(`/moderation/bulk-operations/${id}/retry/`);
|
|
},
|
|
|
|
getLogs: async (id: string): Promise<ApiResponse<{ logs: any[]; count: number }>> => {
|
|
return this.http.get(`/moderation/bulk-operations/${id}/logs/`);
|
|
},
|
|
|
|
getRunning: async (): Promise<ApiResponse<PaginatedResponse<BulkOperation>>> => {
|
|
return this.http.get<PaginatedResponse<BulkOperation>>('/moderation/bulk-operations/running/');
|
|
},
|
|
},
|
|
|
|
// User Moderation
|
|
users: {
|
|
get: async (id: number): Promise<ApiResponse<UserModerationProfile>> => {
|
|
return this.http.get<UserModerationProfile>(`/moderation/users/${id}/`);
|
|
},
|
|
|
|
moderate: async (id: number, data: CreateModerationActionData): Promise<ApiResponse<ModerationAction>> => {
|
|
return this.http.post<ModerationAction>(`/moderation/users/${id}/moderate/`, data);
|
|
},
|
|
|
|
search: async (params: { query?: string; role?: string; has_restrictions?: boolean }): Promise<ApiResponse<PaginatedResponse<any>>> => {
|
|
return this.http.get<PaginatedResponse<any>>('/moderation/users/search/', params);
|
|
},
|
|
|
|
getStats: async (): Promise<ApiResponse<{ total_actions: number; active_actions: number; expired_actions: number }>> => {
|
|
return this.http.get('/moderation/users/stats/');
|
|
},
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Parks API
|
|
// ============================================================================
|
|
|
|
parks = {
|
|
list: async (filters?: ParkFilters): Promise<ApiResponse<PaginatedResponse<Park>>> => {
|
|
return this.http.get<PaginatedResponse<Park>>('/parks/', filters);
|
|
},
|
|
|
|
get: async (slug: string): Promise<ApiResponse<Park>> => {
|
|
return this.http.get<Park>(`/parks/${slug}/`);
|
|
},
|
|
|
|
getRides: async (parkSlug: string, filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
|
|
return this.http.get<PaginatedResponse<Ride>>(`/parks/${parkSlug}/rides/`, filters);
|
|
},
|
|
|
|
getPhotos: async (parkSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<ParkPhoto>>> => {
|
|
return this.http.get<PaginatedResponse<ParkPhoto>>(`/parks/${parkSlug}/photos/`, filters);
|
|
},
|
|
|
|
// Park operators and owners
|
|
operators: {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
|
|
return this.http.get<PaginatedResponse<any>>('/parks/operators/', filters);
|
|
},
|
|
|
|
get: async (slug: string): Promise<ApiResponse<any>> => {
|
|
return this.http.get(`/parks/operators/${slug}/`);
|
|
},
|
|
},
|
|
|
|
owners: {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
|
|
return this.http.get<PaginatedResponse<any>>('/parks/owners/', filters);
|
|
},
|
|
|
|
get: async (slug: string): Promise<ApiResponse<any>> => {
|
|
return this.http.get(`/parks/owners/${slug}/`);
|
|
},
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Rides API
|
|
// ============================================================================
|
|
|
|
rides = {
|
|
list: async (filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
|
|
return this.http.get<PaginatedResponse<Ride>>('/rides/', filters);
|
|
},
|
|
|
|
get: async (parkSlug: string, rideSlug: string): Promise<ApiResponse<Ride>> => {
|
|
return this.http.get<Ride>(`/rides/${parkSlug}/${rideSlug}/`);
|
|
},
|
|
|
|
getPhotos: async (parkSlug: string, rideSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RidePhoto>>> => {
|
|
return this.http.get<PaginatedResponse<RidePhoto>>(`/rides/${parkSlug}/${rideSlug}/photos/`, filters);
|
|
},
|
|
|
|
getReviews: async (parkSlug: string, rideSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideReview>>> => {
|
|
return this.http.get<PaginatedResponse<RideReview>>(`/rides/${parkSlug}/${rideSlug}/reviews/`, filters);
|
|
},
|
|
|
|
createReview: async (parkSlug: string, rideSlug: string, data: CreateRideReviewData): Promise<ApiResponse<RideReview>> => {
|
|
return this.http.post<RideReview>(`/rides/${parkSlug}/${rideSlug}/reviews/`, data);
|
|
},
|
|
|
|
// Manufacturers
|
|
manufacturers: {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<Manufacturer>>> => {
|
|
return this.http.get<PaginatedResponse<Manufacturer>>('/rides/manufacturers/', filters);
|
|
},
|
|
|
|
get: async (slug: string): Promise<ApiResponse<Manufacturer>> => {
|
|
return this.http.get<Manufacturer>(`/rides/manufacturers/${slug}/`);
|
|
},
|
|
|
|
getRides: async (slug: string, filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
|
|
return this.http.get<PaginatedResponse<Ride>>(`/rides/manufacturers/${slug}/rides/`, filters);
|
|
},
|
|
|
|
getModels: async (slug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideModel>>> => {
|
|
return this.http.get<PaginatedResponse<RideModel>>(`/rides/manufacturers/${slug}/models/`, filters);
|
|
},
|
|
},
|
|
|
|
// Designers
|
|
designers: {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
|
|
return this.http.get<PaginatedResponse<any>>('/rides/designers/', filters);
|
|
},
|
|
|
|
get: async (slug: string): Promise<ApiResponse<any>> => {
|
|
return this.http.get(`/rides/designers/${slug}/`);
|
|
},
|
|
},
|
|
|
|
// Ride Models
|
|
models: {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideModel>>> => {
|
|
return this.http.get<PaginatedResponse<RideModel>>('/rides/models/', filters);
|
|
},
|
|
|
|
get: async (manufacturerSlug: string, modelSlug: string): Promise<ApiResponse<RideModel>> => {
|
|
return this.http.get<RideModel>(`/rides/models/${manufacturerSlug}/${modelSlug}/`);
|
|
},
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Statistics API
|
|
// ============================================================================
|
|
|
|
stats = {
|
|
getGlobal: async (): Promise<ApiResponse<GlobalStats>> => {
|
|
return this.http.get<GlobalStats>('/stats/');
|
|
},
|
|
|
|
recalculate: async (): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/stats/recalculate/');
|
|
},
|
|
|
|
getTrending: async (params?: { content_type?: string; time_period?: string }): Promise<ApiResponse<TrendingContent>> => {
|
|
return this.http.get<TrendingContent>('/trending/', params);
|
|
},
|
|
|
|
getNewContent: async (): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/new-content/');
|
|
},
|
|
|
|
triggerTrendingCalculation: async (): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/trending/calculate/');
|
|
},
|
|
|
|
getLatestReviews: async (params?: { limit?: number; park?: string; ride?: string }): Promise<ApiResponse<PaginatedResponse<RideReview>>> => {
|
|
return this.http.get<PaginatedResponse<RideReview>>('/reviews/latest/', params);
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Rankings API
|
|
// ============================================================================
|
|
|
|
rankings = {
|
|
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
|
|
return this.http.get<PaginatedResponse<any>>('/rankings/', filters);
|
|
},
|
|
|
|
calculate: async (): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/rankings/calculate/');
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Health Check API
|
|
// ============================================================================
|
|
|
|
health = {
|
|
check: async (): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/health/');
|
|
},
|
|
|
|
simple: async (): Promise<ApiResponse<{ status: string }>> => {
|
|
return this.http.get('/health/simple/');
|
|
},
|
|
|
|
performance: async (): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/health/performance/');
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Accounts API
|
|
// ============================================================================
|
|
|
|
accounts = {
|
|
getProfile: async (username: string): Promise<ApiResponse<UserProfile>> => {
|
|
return this.http.get<UserProfile>(`/accounts/users/${username}/`);
|
|
},
|
|
|
|
updateProfile: async (data: Partial<UserProfile>): Promise<ApiResponse<UserProfile>> => {
|
|
return this.http.patch<UserProfile>('/accounts/profile/', data);
|
|
},
|
|
|
|
getSettings: async (): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/accounts/settings/');
|
|
},
|
|
|
|
updateSettings: async (data: any): Promise<ApiResponse<any>> => {
|
|
return this.http.patch('/accounts/settings/', data);
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Maps API
|
|
// ============================================================================
|
|
|
|
maps = {
|
|
getParkLocations: async (params?: { bounds?: string; zoom?: number }): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/maps/park-locations/', params);
|
|
},
|
|
|
|
getRideLocations: async (parkSlug: string, params?: { bounds?: string; zoom?: number }): Promise<ApiResponse<any>> => {
|
|
return this.http.get(`/maps/parks/${parkSlug}/ride-locations/`, params);
|
|
},
|
|
|
|
getUnifiedMap: async (params?: { bounds?: string; zoom?: number; include_parks?: boolean; include_rides?: boolean }): Promise<ApiResponse<any>> => {
|
|
return this.http.get('/maps/unified/', params);
|
|
},
|
|
};
|
|
|
|
// ============================================================================
|
|
// Email API
|
|
// ============================================================================
|
|
|
|
email = {
|
|
sendTestEmail: async (data: { to: string; subject: string; message: string }): Promise<ApiResponse<null>> => {
|
|
return this.http.post<null>('/email/send-test/', data);
|
|
},
|
|
|
|
getTemplates: async (): Promise<ApiResponse<any[]>> => {
|
|
return this.http.get('/email/templates/');
|
|
},
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Default Export and Utilities
|
|
// ============================================================================
|
|
|
|
// Create default client instance
|
|
export const apiClient = new ThrillWikiApiClient();
|
|
|
|
// Utility functions for common operations
|
|
export const apiUtils = {
|
|
// Set authentication token for all requests
|
|
setAuthToken: (token: string | null) => {
|
|
apiClient.setAuthToken(token);
|
|
},
|
|
|
|
// Check if response is successful
|
|
isSuccess: <T>(response: ApiResponse<T>): response is ApiResponse<T> & { status: 'success'; data: T } => {
|
|
return response.status === 'success' && response.data !== null;
|
|
},
|
|
|
|
// Check if response is an error
|
|
isError: <T>(response: ApiResponse<T>): response is ApiResponse<T> & { status: 'error'; error: NonNullable<ApiResponse<T>['error']> } => {
|
|
return response.status === 'error' && response.error !== null;
|
|
},
|
|
|
|
// Extract data from successful response or throw error
|
|
unwrap: <T>(response: ApiResponse<T>): T => {
|
|
if (apiUtils.isSuccess(response)) {
|
|
return response.data;
|
|
}
|
|
throw new Error(response.error?.message || 'API request failed');
|
|
},
|
|
|
|
// Handle paginated responses
|
|
extractPaginatedData: <T>(response: ApiResponse<PaginatedResponse<T>>): T[] => {
|
|
if (apiUtils.isSuccess(response)) {
|
|
return response.data.results;
|
|
}
|
|
return [];
|
|
},
|
|
|
|
// Build query string from filters
|
|
buildQueryString: (filters: Record<string, any>): string => {
|
|
const params = new URLSearchParams();
|
|
|
|
Object.entries(filters).forEach(([key, value]) => {
|
|
if (value !== undefined && value !== null && value !== '') {
|
|
if (Array.isArray(value)) {
|
|
value.forEach(v => params.append(key, String(v)));
|
|
} else {
|
|
params.append(key, String(value));
|
|
}
|
|
}
|
|
});
|
|
|
|
return params.toString();
|
|
},
|
|
|
|
// Format error message for display
|
|
formatError: (error: ApiResponse<any>['error']): string => {
|
|
if (!error) return 'Unknown error occurred';
|
|
|
|
if (error.details && typeof error.details === 'object') {
|
|
// Handle validation errors
|
|
const fieldErrors = Object.entries(error.details)
|
|
.map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
|
|
.join('; ');
|
|
|
|
return fieldErrors || error.message;
|
|
}
|
|
|
|
return error.message;
|
|
},
|
|
|
|
// Check if user has required role
|
|
hasRole: (user: UserProfile | null, requiredRole: UserProfile['role']): boolean => {
|
|
if (!user) return false;
|
|
|
|
const roleHierarchy = ['USER', 'MODERATOR', 'ADMIN', 'SUPERUSER'];
|
|
const userRoleIndex = roleHierarchy.indexOf(user.role);
|
|
const requiredRoleIndex = roleHierarchy.indexOf(requiredRole);
|
|
|
|
return userRoleIndex >= requiredRoleIndex;
|
|
},
|
|
|
|
// Check if user can moderate
|
|
canModerate: (user: UserProfile | null): boolean => {
|
|
return apiUtils.hasRole(user, 'MODERATOR');
|
|
},
|
|
|
|
// Check if user is admin
|
|
isAdmin: (user: UserProfile | null): boolean => {
|
|
return apiUtils.hasRole(user, 'ADMIN');
|
|
},
|
|
};
|
|
|
|
// Export types for convenience
|
|
export type {
|
|
ApiResponse,
|
|
PaginatedResponse,
|
|
ModerationReport,
|
|
ModerationQueue,
|
|
ModerationAction,
|
|
BulkOperation,
|
|
UserModerationProfile,
|
|
Park,
|
|
Ride,
|
|
Manufacturer,
|
|
RideModel,
|
|
UserProfile,
|
|
GlobalStats,
|
|
TrendingContent,
|
|
} from './types-api';
|
|
|
|
export default apiClient;
|