Spaces:
Running
Running
/** | |
* 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; | |
} | |
} | |
} | |