243 lines
7.4 KiB
JavaScript
243 lines
7.4 KiB
JavaScript
/**
|
|
* API layer for Calendink Provider ESP32-S3 dashboard.
|
|
*
|
|
* VITE_API_BASE controls the backend URL:
|
|
* - Development (PC): "http://<ESP32_IP>" (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<Array<{id: number, name: string}>>}
|
|
*/
|
|
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<Array<{id: number, user_id: number, title: string, due_date: number, completed: boolean}>>}
|
|
*/
|
|
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();
|
|
}
|