/** * API layer for Calendink Provider ESP32-S3 dashboard. * * VITE_API_BASE controls the backend URL: * - Development (PC): "http://" (set in .env.development) * - Production (ESP32): "" (empty = relative URLs, same origin) */ const API_BASE = import.meta.env.VITE_API_BASE || ''; /** * Fetch system information from the ESP32. * @returns {Promise<{chip: string, freeHeap: number, uptime: number, firmware: string, connection: string}>} */ export async function getSystemInfo() { const res = await fetch(`${API_BASE}/api/system/info`); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } const data = await res.json(); return { ...data, freeHeap: data.free_heap }; } /** * Send a reboot command to the ESP32. * @returns {Promise<{message: string}>} */ export async function reboot() { const res = await fetch(`${API_BASE}/api/system/reboot`, { method: 'POST', }); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return res.json(); } /** * Fetch OTA status from the ESP32. * @returns {Promise<{active_slot: number, active_partition: string, target_partition: string, partitions: any[], running_firmware_label: string, running_firmware_slot: number}>} */ export async function getOTAStatus() { const res = await fetch(`${API_BASE}/api/ota/status`); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return res.json(); } /** * Upload a new frontend binary image. * @param {File} file The binary file to upload. * @returns {Promise<{status: string, message: string}>} */ export async function uploadOTAFrontend(file) { const res = await fetch(`${API_BASE}/api/ota/frontend`, { method: 'POST', body: file, // Send the raw file Blob/Buffer headers: { // Let the browser set Content-Type for the binary payload, // or we could force application/octet-stream. 'Content-Type': 'application/octet-stream' } }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Upload failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } /** * Upload a new firmware binary image. * @param {File} file The firmware binary file to upload. * @returns {Promise<{status: string, message: string}>} */ export async function uploadOTAFirmware(file) { const res = await fetch(`${API_BASE}/api/ota/firmware`, { method: 'POST', body: file, headers: { 'Content-Type': 'application/octet-stream' } }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Upload failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } /** * Upload a universal .bundle image (FW + WWW). * @param {File} file The bundle binary file to upload. * @returns {Promise<{status: string, message: string}>} */ export async function uploadOTABundle(file) { const res = await fetch(`${API_BASE}/api/ota/bundle`, { method: 'POST', body: file, headers: { 'Content-Type': 'application/octet-stream' } }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Upload failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } // ─── User Management ───────────────────────────────────────────────────────── /** * Fetch all users. * @returns {Promise>} */ export async function getUsers() { const res = await fetch(`${API_BASE}/api/users`); if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); return res.json(); } /** * Create a new user. * @param {string} name * @returns {Promise<{id: number, name: string}>} */ export async function addUser(name) { const res = await fetch(`${API_BASE}/api/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } /** * Delete a user and all their tasks. * @param {number} id * @returns {Promise<{status: string}>} */ export async function removeUser(id) { const res = await fetch(`${API_BASE}/api/users?id=${id}`, { method: 'DELETE' }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } // ─── Task Management ───────────────────────────────────────────────────────── /** * Fetch tasks for a specific user. * @param {number} userId * @returns {Promise>} */ export async function getTasks(userId) { const res = await fetch(`${API_BASE}/api/tasks?user_id=${userId}`); if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); return res.json(); } /** * Fetch top 3 upcoming tasks per user (for Dashboard). * @returns {Promise<{users: Array<{id: number, name: string, tasks: Array}>}>} */ export async function getUpcomingTasks() { const res = await fetch(`${API_BASE}/api/tasks/upcoming`); if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); return res.json(); } /** * Create a new task. * @param {number} userId * @param {string} title * @param {number} dueDate Unix timestamp in seconds * @returns {Promise<{id: number, user_id: number, title: string, due_date: number, completed: boolean}>} */ export async function addTask(userId, title, dueDate) { const res = await fetch(`${API_BASE}/api/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userId, title, due_date: dueDate }) }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } /** * Update a task (partial update — only include fields you want to change). * @param {number} id * @param {Object} fields - { title?: string, due_date?: number, completed?: boolean } * @returns {Promise<{status: string}>} */ export async function updateTask(id, fields) { const res = await fetch(`${API_BASE}/api/tasks/update`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, ...fields }) }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); } /** * Delete a task. * @param {number} id * @returns {Promise<{status: string}>} */ export async function deleteTask(id) { const res = await fetch(`${API_BASE}/api/tasks?id=${id}`, { method: 'DELETE' }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed (${res.status}): ${errorText || res.statusText}`); } return res.json(); }