/** * api.js – Zentraler Service für alle API-Aufrufe * ================================================ * * Dieses Modul kapselt die gesamte Kommunikation mit der REST-API. * Die UI-Komponenten importieren nur die benötigten Funktionen – * sie müssen nicht wissen, wie die Fetch-Aufrufe intern funktionieren. * * API-Basis: https://webdev.iten-web.ch/10003/api * * Best-Practice-Routing mit REST-Pfaden: * GET https://webdev.iten-web.ch/10003/api/todos?page=1&limit=10 * GET https://webdev.iten-web.ch/10003/api/todos/42 */ /** Basis-URL der API – hier zentral änderbar */ const BASE_URL = 'https://webdev.iten-web.ch/10003/api' /** * buildUrl – Erstellt eine vollständige Anfrage-URL. * * @param {string} path - API-Pfad, z.B. "/todos" oder "/todos/5" * @param {object} [params] - Optionale Query-Parameter als Schlüssel-Wert-Objekt * @returns {string} - Fertige URL als String * * Beispiel: * buildUrl('/todos', { page: 2, limit: 10 }) * → "https://webdev.iten-web.ch/10003/api/todos?page=2&limit=10" */ function buildUrl(path, params = {}) { const normalizedPath = path.startsWith('/') ? path : `/${path}` const url = new URL(`${BASE_URL}${normalizedPath}`) // Weitere Parameter anhängen (leere Werte werden übersprungen) for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== '') { url.searchParams.set(key, value) } } return url.toString() } // ============================================================= // GET /todos – Liste abrufen (mit Pagination) // ============================================================= /** * fetchTodos – Ruft eine paginierte Liste von ToDos ab. * * @param {number} [page=1] - Seitennummer (1-basiert) * @param {number} [limit=10] - Anzahl Einträge pro Seite * @returns {Promise} - Array von Todo-Objekten * * Todo-Objekt: { id, userId, title, completed } */ export async function fetchTodos(page = 1, limit = 10) { const url = buildUrl('/todos', { page, limit }) const response = await fetch(url) // HTTP-Fehler (4xx, 5xx) werden von fetch() NICHT automatisch geworfen – // wir prüfen response.ok manuell und werfen ggf. einen Fehler if (!response.ok) { throw new Error(`Fehler beim Laden der ToDos (HTTP ${response.status})`) } // response.json() liest den Response-Body und parst ihn als JSON return response.json() } // ============================================================= // GET /todos/{id} – Einzelnes ToDo abrufen // ============================================================= /** * fetchTodoById – Ruft ein einzelnes ToDo per ID ab. * * @param {number} id - ID des gesuchten ToDos * @returns {Promise} - Todo-Objekt */ export async function fetchTodoById(id) { const url = buildUrl(`/todos/${id}`) const response = await fetch(url) if (!response.ok) { throw new Error(`ToDo #${id} nicht gefunden (HTTP ${response.status})`) } return response.json() } // ============================================================= // POST /todos – Neues ToDo erstellen // ============================================================= /** * createTodo – Erstellt ein neues ToDo. * * @param {{ userId: number, title: string, completed?: boolean }} todo * @returns {Promise} - Erstelltes ToDo-Objekt (inkl. vergebener id) * * Beispiel-Aufruf: * createTodo({ userId: 1, title: "Einkaufen gehen" }) */ export async function createTodo(todo) { const url = buildUrl('/todos') const response = await fetch(url, { method: 'POST', headers: { // Teilt dem Server mit, dass wir JSON senden 'Content-Type': 'application/json', }, // JSON.stringify() wandelt das JavaScript-Objekt in einen JSON-String um body: JSON.stringify(todo), }) if (!response.ok) { throw new Error(`Fehler beim Erstellen des ToDos (HTTP ${response.status})`) } // Der Server antwortet mit 201 Created + dem neuen Objekt return response.json() } // ============================================================= // PATCH /todos/{id} – ToDo teilweise aktualisieren // ============================================================= /** * patchTodo – Aktualisiert einzelne Felder eines ToDos. * * Im Unterschied zu PUT werden bei PATCH nur die angegebenen Felder * geändert – alle anderen bleiben unverändert. * * @param {number} id - Todo-ID * @param {{ userId?: number, title?: string, completed?: boolean }} changes * @returns {Promise} - Aktualisiertes ToDo * * Beispiel-Aufruf (nur completed ändern): * patchTodo(5, { completed: true }) */ export async function patchTodo(id, changes) { const url = buildUrl(`/todos/${id}`) const response = await fetch(url, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(changes), }) if (!response.ok) { throw new Error(`Fehler beim Aktualisieren von ToDo #${id} (HTTP ${response.status})`) } return response.json() } // ============================================================= // PUT /todos/{id} – ToDo vollständig ersetzen // ============================================================= /** * putTodo – Ersetzt ein ToDo vollständig (alle Felder müssen angegeben werden). * * @param {number} id - Todo-ID * @param {{ userId: number, title: string, completed: boolean }} todo * @returns {Promise} - Ersetztes ToDo */ export async function putTodo(id, todo) { const url = buildUrl(`/todos/${id}`) const response = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(todo), }) if (!response.ok) { throw new Error(`Fehler beim Ersetzen von ToDo #${id} (HTTP ${response.status})`) } return response.json() } // ============================================================= // DELETE /todos/{id} – ToDo löschen // ============================================================= /** * deleteTodo – Löscht ein ToDo anhand seiner ID. * * @param {number} id - Todo-ID * @returns {Promise} - Kein Rückgabewert (204 No Content) */ export async function deleteTodo(id) { const url = buildUrl(`/todos/${id}`) const response = await fetch(url, { method: 'DELETE', }) if (!response.ok) { throw new Error(`Fehler beim Löschen von ToDo #${id} (HTTP ${response.status})`) } // DELETE gibt HTTP 204 No Content zurück → kein response.json() nötig }