mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:11:13 -05:00
Refactor API structure and add comprehensive user management features
- 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
This commit is contained in:
729
docs/lib-api.ts
Normal file
729
docs/lib-api.ts
Normal file
@@ -0,0 +1,729 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user