204 lines
6.5 KiB
JavaScript
204 lines
6.5 KiB
JavaScript
/**
|
||
* 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>} - 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<object>} - 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<object>} - 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<object>} - 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<object>} - 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<void>} - 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
|
||
}
|