Spaces:
Runtime error
Runtime error
| /** | |
| * API Client Module | |
| * Handles all API communication with authentication | |
| */ | |
| export class ApiClient { | |
| constructor(authManager) { | |
| this.authManager = authManager; | |
| } | |
| async authenticatedFetch(url, options = {}) { | |
| const headers = { | |
| ...this.authManager.getAuthHeaders(), | |
| ...options.headers | |
| }; | |
| const response = await fetch(url, { | |
| ...options, | |
| headers | |
| }); | |
| if (response.status === 401) { | |
| // Token expired or invalid | |
| this.authManager.clearAuthData(); | |
| window.location.href = '/login'; | |
| return null; | |
| } | |
| return response; | |
| } | |
| async loadFormOptions() { | |
| try { | |
| const [utilityResponse, phenologyResponse, categoriesResponse] = await Promise.all([ | |
| this.authenticatedFetch('/api/utilities'), | |
| this.authenticatedFetch('/api/phenology-stages'), | |
| this.authenticatedFetch('/api/photo-categories') | |
| ]); | |
| if (!utilityResponse || !phenologyResponse || !categoriesResponse) { | |
| throw new Error('Failed to load form options'); | |
| } | |
| const [utilityData, phenologyData, categoriesData] = await Promise.all([ | |
| utilityResponse.json(), | |
| phenologyResponse.json(), | |
| categoriesResponse.json() | |
| ]); | |
| return { | |
| utilities: utilityData.utilities, | |
| phenologyStages: phenologyData.stages, | |
| photoCategories: categoriesData.categories | |
| }; | |
| } catch (error) { | |
| console.error('Error loading form options:', error); | |
| throw error; | |
| } | |
| } | |
| async saveTree(treeData) { | |
| const response = await this.authenticatedFetch('/api/trees', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(treeData) | |
| }); | |
| if (!response) return null; | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.detail || 'Unknown error'); | |
| } | |
| return await response.json(); | |
| } | |
| async updateTree(treeId, treeData) { | |
| const response = await this.authenticatedFetch(`/api/trees/${treeId}`, { | |
| method: 'PUT', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(treeData) | |
| }); | |
| if (!response) return null; | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.detail || 'Unknown error'); | |
| } | |
| return await response.json(); | |
| } | |
| async deleteTree(treeId) { | |
| const response = await this.authenticatedFetch(`/api/trees/${treeId}`, { | |
| method: 'DELETE' | |
| }); | |
| if (!response) return null; | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.detail || 'Unknown error'); | |
| } | |
| return true; | |
| } | |
| async loadTrees(limit = 20) { | |
| const response = await this.authenticatedFetch(`/api/trees?limit=${limit}`); | |
| if (!response) return []; | |
| if (!response.ok) { | |
| throw new Error('Failed to load trees'); | |
| } | |
| return await response.json(); | |
| } | |
| async loadTree(treeId) { | |
| const response = await this.authenticatedFetch(`/api/trees/${treeId}`); | |
| if (!response) return null; | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch tree data'); | |
| } | |
| return await response.json(); | |
| } | |
| async loadTreeCodes() { | |
| const response = await this.authenticatedFetch('/api/tree-codes'); | |
| if (!response) return []; | |
| if (!response.ok) { | |
| throw new Error('Failed to load tree codes'); | |
| } | |
| const data = await response.json(); | |
| return data.tree_codes || []; | |
| } | |
| async searchTreeSuggestions(query, limit = 10) { | |
| const response = await this.authenticatedFetch( | |
| `/api/tree-suggestions?query=${encodeURIComponent(query)}&limit=${limit}` | |
| ); | |
| if (!response) return []; | |
| if (!response.ok) { | |
| throw new Error('Failed to search tree suggestions'); | |
| } | |
| const data = await response.json(); | |
| return data.suggestions || []; | |
| } | |
| async uploadFile(file, type, category = null) { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| if (category) { | |
| formData.append('category', category); | |
| } | |
| const endpoint = type === 'image' ? '/api/upload/image' : '/api/upload/audio'; | |
| let resJson = null; | |
| try { | |
| const response = await fetch(endpoint, { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${this.authManager.authToken}` | |
| }, | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| // Avoid noisy telemetry; just throw error | |
| throw new Error('Upload failed'); | |
| } | |
| resJson = await response.json(); | |
| return resJson; | |
| } catch (e) { | |
| // Network or other errors | |
| throw e; | |
| } | |
| } | |
| } | |