const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3010'; class ApiClient { private baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; } private getHeaders(): HeadersInit { const token = localStorage.getItem('token'); return { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }; } private async request(endpoint: string, options?: RequestInit): Promise { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { ...this.getHeaders(), ...options?.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({ error: 'Request failed' })); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); } // Auth async login(email: string, password: string) { return this.request<{ token: string; user: any }>('/api/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }); } async getMe() { return this.request<{ user: any }>('/api/auth/me'); } // Dashboard async getDashboardStats() { return this.request('/api/dashboard/stats'); } async getDashboardActivity() { return this.request('/api/dashboard/activity'); } // Stores async getStores() { return this.request<{ stores: any[] }>('/api/stores'); } async getStore(id: number) { return this.request(`/api/stores/${id}`); } async createStore(data: any) { return this.request('/api/stores', { method: 'POST', body: JSON.stringify(data), }); } async updateStore(id: number, data: any) { return this.request(`/api/stores/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async getDispensaries() { return this.request<{ dispensaries: any[] }>('/api/dispensaries'); } async getDispensary(slug: string) { return this.request(`/api/dispensaries/${slug}`); } async updateDispensary(id: number, data: any) { return this.request(`/api/dispensaries/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteStore(id: number) { return this.request(`/api/stores/${id}`, { method: 'DELETE', }); } async scrapeStore(id: number, parallel?: number, userAgent?: string) { return this.request(`/api/stores/${id}/scrape`, { method: 'POST', body: JSON.stringify({ parallel, userAgent }), }); } async downloadStoreImages(id: number) { return this.request(`/api/stores/${id}/download-images`, { method: 'POST', }); } async discoverStoreCategories(id: number) { return this.request(`/api/stores/${id}/discover-categories`, { method: 'POST', }); } async debugScrapeStore(id: number) { return this.request(`/api/stores/${id}/debug-scrape`, { method: 'POST', }); } async getStoreBrands(id: number) { return this.request<{ brands: string[] }>(`/api/stores/${id}/brands`); } async getStoreSpecials(id: number, date?: string) { const params = date ? `?date=${date}` : ''; return this.request<{ specials: any[]; date: string }>(`/api/stores/${id}/specials${params}`); } // Categories async getCategories(storeId?: number) { const params = storeId ? `?store_id=${storeId}` : ''; return this.request<{ categories: any[] }>(`/api/categories${params}`); } async getCategoryTree(storeId: number) { return this.request(`/api/categories/tree?store_id=${storeId}`); } // Products async getProducts(params?: any) { const query = new URLSearchParams(params).toString(); return this.request(`/api/products${query ? `?${query}` : ''}`); } async getProduct(id: number) { return this.request(`/api/products/${id}`); } // Campaigns async getCampaigns() { return this.request<{ campaigns: any[] }>('/api/campaigns'); } async getCampaign(id: number) { return this.request(`/api/campaigns/${id}`); } async createCampaign(data: any) { return this.request('/api/campaigns', { method: 'POST', body: JSON.stringify(data), }); } async updateCampaign(id: number, data: any) { return this.request(`/api/campaigns/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteCampaign(id: number) { return this.request(`/api/campaigns/${id}`, { method: 'DELETE', }); } async addProductToCampaign(campaignId: number, productId: number, displayOrder?: number) { return this.request(`/api/campaigns/${campaignId}/products`, { method: 'POST', body: JSON.stringify({ product_id: productId, display_order: displayOrder }), }); } async removeProductFromCampaign(campaignId: number, productId: number) { return this.request(`/api/campaigns/${campaignId}/products/${productId}`, { method: 'DELETE', }); } // Analytics async getAnalyticsOverview(days?: number) { return this.request(`/api/analytics/overview${days ? `?days=${days}` : ''}`); } async getProductAnalytics(id: number, days?: number) { return this.request(`/api/analytics/products/${id}${days ? `?days=${days}` : ''}`); } async getCampaignAnalytics(id: number, days?: number) { return this.request(`/api/analytics/campaigns/${id}${days ? `?days=${days}` : ''}`); } // Settings async getSettings() { return this.request<{ settings: any[] }>('/api/settings'); } async updateSetting(key: string, value: string) { return this.request(`/api/settings/${key}`, { method: 'PUT', body: JSON.stringify({ value }), }); } async updateSettings(settings: Array<{ key: string; value: string }>) { return this.request('/api/settings', { method: 'PUT', body: JSON.stringify({ settings }), }); } // Proxies async getProxies() { return this.request<{ proxies: any[] }>('/api/proxies'); } async getProxy(id: number) { return this.request(`/api/proxies/${id}`); } async addProxy(data: any) { return this.request('/api/proxies', { method: 'POST', body: JSON.stringify(data), }); } async addProxiesBulk(proxies: any[]) { return this.request('/api/proxies/bulk', { method: 'POST', body: JSON.stringify({ proxies }), }); } async testProxy(id: number) { return this.request(`/api/proxies/${id}/test`, { method: 'POST', }); } async testAllProxies() { return this.request<{ jobId: number; message: string }>('/api/proxies/test-all', { method: 'POST', }); } async getProxyTestJob(jobId: number) { return this.request<{ job: any }>(`/api/proxies/test-job/${jobId}`); } async getActiveProxyTestJob() { return this.request<{ job: any }>('/api/proxies/test-job'); } async cancelProxyTestJob(jobId: number) { return this.request<{ message: string }>(`/api/proxies/test-job/${jobId}/cancel`, { method: 'POST', }); } async updateProxy(id: number, data: any) { return this.request(`/api/proxies/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async deleteProxy(id: number) { return this.request(`/api/proxies/${id}`, { method: 'DELETE', }); } async updateProxyLocations() { return this.request<{ message: string }>('/api/proxies/update-locations', { method: 'POST', }); } // Logs async getLogs(limit?: number, level?: string, category?: string) { const params = new URLSearchParams(); if (limit) params.append('limit', limit.toString()); if (level) params.append('level', level); if (category) params.append('category', category); return this.request<{ logs: any[] }>(`/api/logs?${params.toString()}`); } async clearLogs() { return this.request('/api/logs', { method: 'DELETE', }); } // Scraper Monitor async getActiveScrapers() { return this.request('/api/scraper-monitor/active'); } async getScraperHistory(storeId?: number) { const params = storeId ? `?store_id=${storeId}` : ''; return this.request(`/api/scraper-monitor/history${params}`); } async getJobStats(dispensaryId?: number) { const params = dispensaryId ? `?dispensary_id=${dispensaryId}` : ''; return this.request(`/api/scraper-monitor/jobs/stats${params}`); } async getActiveJobs(dispensaryId?: number) { const params = dispensaryId ? `?dispensary_id=${dispensaryId}` : ''; return this.request(`/api/scraper-monitor/jobs/active${params}`); } async getRecentJobs(options?: { limit?: number; dispensaryId?: number; status?: string }) { const params = new URLSearchParams(); if (options?.limit) params.append('limit', options.limit.toString()); if (options?.dispensaryId) params.append('dispensary_id', options.dispensaryId.toString()); if (options?.status) params.append('status', options.status); const queryString = params.toString() ? `?${params.toString()}` : ''; return this.request(`/api/scraper-monitor/jobs/recent${queryString}`); } async getWorkerStats(dispensaryId?: number) { const params = dispensaryId ? `?dispensary_id=${dispensaryId}` : ''; return this.request(`/api/scraper-monitor/jobs/workers${params}`); } // Change Approval async getChanges(status?: 'pending' | 'approved' | 'rejected') { const params = status ? `?status=${status}` : ''; return this.request<{ changes: any[] }>(`/api/changes${params}`); } async getChangeStats() { return this.request<{ pending_count: number; pending_recrawl_count: number; approved_count: number; rejected_count: number; }>('/api/changes/stats'); } async approveChange(changeId: number) { return this.request<{ message: string; dispensary: any; requires_recrawl: boolean; }>(`/api/changes/${changeId}/approve`, { method: 'POST', }); } async rejectChange(changeId: number, reason?: string) { return this.request<{ message: string; change: any; }>(`/api/changes/${changeId}/reject`, { method: 'POST', body: JSON.stringify({ reason }), }); } // Dispensary Products, Brands, Specials async getDispensaryProducts(slug: string, category?: number) { const params = category ? `?category=${category}` : ''; return this.request<{ products: any[] }>(`/api/dispensaries/${slug}/products${params}`); } async getDispensaryBrands(slug: string) { return this.request<{ brands: Array<{ brand: string; product_count: number }> }>(`/api/dispensaries/${slug}/brands`); } async getDispensarySpecials(slug: string) { return this.request<{ specials: any[] }>(`/api/dispensaries/${slug}/specials`); } // API Permissions async getApiPermissions() { return this.request<{ permissions: any[] }>('/api/api-permissions'); } async createApiPermission(data: { user_name: string; allowed_ips?: string; allowed_domains?: string }) { return this.request<{ permission: any; message: string }>('/api/api-permissions', { method: 'POST', body: JSON.stringify(data), }); } async updateApiPermission(id: number, data: { user_name?: string; allowed_ips?: string; allowed_domains?: string; is_active?: number }) { return this.request<{ permission: any }>(`/api/api-permissions/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } async toggleApiPermission(id: number) { return this.request<{ permission: any }>(`/api/api-permissions/${id}/toggle`, { method: 'PATCH', }); } async deleteApiPermission(id: number) { return this.request<{ message: string }>(`/api/api-permissions/${id}`, { method: 'DELETE', }); } // Crawler Schedule async getGlobalSchedule() { return this.request<{ schedules: any[] }>('/api/schedule/global'); } async updateGlobalSchedule(type: string, data: { enabled?: boolean; interval_hours?: number; run_time?: string }) { return this.request<{ schedule: any; message: string }>(`/api/schedule/global/${type}`, { method: 'PUT', body: JSON.stringify(data), }); } async getStoreSchedules() { return this.request<{ stores: any[] }>('/api/schedule/stores'); } async getStoreSchedule(storeId: number) { return this.request<{ schedule: any }>(`/api/schedule/stores/${storeId}`); } async updateStoreSchedule(storeId: number, data: any) { return this.request<{ schedule: any }>(`/api/schedule/stores/${storeId}`, { method: 'PUT', body: JSON.stringify(data), }); } async getCrawlJobs(limit?: number) { const params = limit ? `?limit=${limit}` : ''; return this.request<{ jobs: any[] }>(`/api/schedule/jobs${params}`); } async getStoreCrawlJobs(storeId: number, limit?: number) { const params = limit ? `?limit=${limit}` : ''; return this.request<{ jobs: any[] }>(`/api/schedule/jobs/store/${storeId}${params}`); } async cancelCrawlJob(jobId: number) { return this.request<{ success: boolean; message: string }>(`/api/schedule/jobs/${jobId}/cancel`, { method: 'POST', }); } async triggerStoreCrawl(storeId: number) { return this.request<{ job: any; message: string }>(`/api/schedule/trigger/store/${storeId}`, { method: 'POST', }); } async triggerAllCrawls() { return this.request<{ jobs_created: number; message: string }>('/api/schedule/trigger/all', { method: 'POST', }); } async restartScheduler() { return this.request<{ message: string }>('/api/schedule/restart', { method: 'POST', }); } } export const api = new ApiClient(API_URL);