Add REST-compliant PUT/DELETE endpoints and invitation validation
- Replace POST /invitation/:id/accept|decline with PUT /invitation/:id - Add DELETE /invitation/:id (full CRUD: GET, POST, PUT, DELETE) - Validate invitation recipient exists before creating invitation (404) - Block self-invitation (400) - Propagate server error messages to inline card feedback - Add Loeschen button to invitation cards (demonstrates DELETE in UI) - Improve empty state with icon and descriptive text
This commit is contained in:
parent
fdffc08417
commit
ae3692697b
@ -1,114 +1,205 @@
|
||||
/**
|
||||
* eventCard.js – Erstellt interaktive Event-Karten als DOM-Elemente.
|
||||
* Jede Karte zeigt Titel, Datum, Veranstaltungsort sowie Speichern- und Einladen-Buttons.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Erstellt eine vollständige Event-Karte als <article>-Element.
|
||||
* @param {Object} event - Event-Objekt mit id, name, date, time, venue, category
|
||||
* @returns {HTMLElement} Fertig aufgebautes Karten-Element
|
||||
*/
|
||||
export function createEventCard(event) {
|
||||
|
||||
const article = document.createElement("article");
|
||||
article.className = "event-card";
|
||||
|
||||
// --- TITEL ---
|
||||
const title = document.createElement("h3");
|
||||
title.className = "event-card__title";
|
||||
title.textContent = event.name;
|
||||
|
||||
// --- DATUM & UHRZEIT formatieren (Schweizer Format: dd.mm.yyyy) ---
|
||||
const formattedDate = event.date
|
||||
? new Date(event.date).toLocaleDateString("de-CH", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric"
|
||||
})
|
||||
: "Date not available";
|
||||
: "Datum unbekannt";
|
||||
|
||||
const formattedTime = event.time
|
||||
? event.time.slice(0, 5)
|
||||
: "";
|
||||
|
||||
const dateTime = formattedTime
|
||||
? `${formattedDate}, ${formattedTime}`
|
||||
: formattedDate;
|
||||
const formattedTime = event.time ? event.time.slice(0, 5) : "";
|
||||
const dateTime = formattedTime ? `${formattedDate}, ${formattedTime}` : formattedDate;
|
||||
|
||||
const date = document.createElement("p");
|
||||
date.className = "event-card__date";
|
||||
date.textContent = dateTime;
|
||||
|
||||
// --- VERANSTALTUNGSORT ---
|
||||
const venue = document.createElement("p");
|
||||
venue.className = "event-card__venue";
|
||||
venue.textContent = event.venue;
|
||||
venue.textContent = event.venue || "Ort unbekannt";
|
||||
|
||||
article.append(title, date, venue);
|
||||
|
||||
// =========================
|
||||
// BUTTON CONTAINER
|
||||
// =========================
|
||||
// --- BUTTON-GRUPPE ---
|
||||
const buttonContainer = document.createElement("div");
|
||||
buttonContainer.className = "d-flex gap-2 mt-2";
|
||||
buttonContainer.className = "d-flex gap-2 mt-2 flex-wrap";
|
||||
|
||||
// =========================
|
||||
// SAVE BUTTON
|
||||
// SPEICHERN-BUTTON
|
||||
// Speichert das Event im localStorage, verhindert Duplikate
|
||||
// =========================
|
||||
const saveBtn = document.createElement("button");
|
||||
saveBtn.textContent = "Save";
|
||||
saveBtn.type = "button";
|
||||
saveBtn.className = "btn btn-outline-primary btn-sm";
|
||||
|
||||
// Prüfen ob bereits gespeichert → Button-Text entsprechend setzen
|
||||
const alreadySaved = isAlreadySaved(event.id);
|
||||
saveBtn.textContent = alreadySaved ? "Gespeichert ✓" : "Speichern";
|
||||
if (alreadySaved) saveBtn.disabled = true;
|
||||
|
||||
saveBtn.addEventListener("click", () => {
|
||||
|
||||
if (!window.currentUser) {
|
||||
alert("Please login to save events");
|
||||
return;
|
||||
}
|
||||
|
||||
const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]");
|
||||
|
||||
// prevent duplicates
|
||||
// Duplikat-Schutz
|
||||
if (saved.find(e => e.id === event.id)) {
|
||||
alert("Event already saved");
|
||||
saveBtn.textContent = "Gespeichert ✓";
|
||||
saveBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
saved.push(event);
|
||||
localStorage.setItem("savedEvents", JSON.stringify(saved));
|
||||
|
||||
alert("Event saved!");
|
||||
// Visuelles Feedback statt alert()
|
||||
saveBtn.textContent = "Gespeichert ✓";
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.classList.replace("btn-outline-primary", "btn-success");
|
||||
});
|
||||
|
||||
saveBtn.disabled = !window.currentUser;
|
||||
|
||||
// =========================
|
||||
// INVITE BUTTON
|
||||
// EINLADEN-BUTTON
|
||||
// Zeigt ein Inline-Formular an (kein browser-prompt())
|
||||
// =========================
|
||||
const inviteBtn = document.createElement("button");
|
||||
inviteBtn.textContent = "Invite";
|
||||
inviteBtn.type = "button";
|
||||
inviteBtn.textContent = "Einladen";
|
||||
inviteBtn.className = "btn btn-primary btn-sm";
|
||||
inviteBtn.disabled = false;
|
||||
|
||||
inviteBtn.addEventListener("click", async () => {
|
||||
|
||||
inviteBtn.addEventListener("click", () => {
|
||||
if (!window.currentUser) {
|
||||
alert("Please login to invite users");
|
||||
showCardMessage(article, "Bitte einloggen um Einladungen zu senden.", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
const toUser = prompt("Enter username to invite:");
|
||||
if (!toUser) return;
|
||||
|
||||
await fetch("http://localhost:3000/api/invitation", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Username": window.currentUser
|
||||
},
|
||||
body: JSON.stringify({
|
||||
toUser,
|
||||
eventId: event.id,
|
||||
eventName: event.name
|
||||
})
|
||||
});
|
||||
|
||||
alert("Invitation sent!");
|
||||
// Formular ein-/ausblenden (Toggle)
|
||||
const existing = article.querySelector(".invite-form");
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
} else {
|
||||
article.appendChild(createInviteForm(event, article));
|
||||
}
|
||||
});
|
||||
|
||||
inviteBtn.disabled = !window.currentUser;
|
||||
|
||||
// =========================
|
||||
// APPEND BUTTONS
|
||||
// =========================
|
||||
buttonContainer.append(saveBtn, inviteBtn);
|
||||
article.appendChild(buttonContainer);
|
||||
|
||||
return article;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// HILFSFUNKTION – Einladungsformular
|
||||
// Erstellt ein kleines Inline-Formular mit Benutzername-Eingabe
|
||||
// =========================
|
||||
function createInviteForm(event, card) {
|
||||
const form = document.createElement("div");
|
||||
form.className = "invite-form mt-2 d-flex gap-2 align-items-center flex-wrap";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.className = "form-control form-control-sm";
|
||||
input.placeholder = "Benutzername eingeben";
|
||||
input.setAttribute("aria-label", "Benutzername für Einladung");
|
||||
|
||||
const sendBtn = document.createElement("button");
|
||||
sendBtn.type = "button";
|
||||
sendBtn.textContent = "Senden";
|
||||
sendBtn.className = "btn btn-primary btn-sm";
|
||||
|
||||
const cancelBtn = document.createElement("button");
|
||||
cancelBtn.type = "button";
|
||||
cancelBtn.textContent = "Abbrechen";
|
||||
cancelBtn.className = "btn btn-outline-secondary btn-sm";
|
||||
|
||||
cancelBtn.addEventListener("click", () => form.remove());
|
||||
|
||||
sendBtn.addEventListener("click", async () => {
|
||||
const toUser = input.value.trim();
|
||||
if (!toUser) {
|
||||
input.classList.add("is-invalid");
|
||||
return;
|
||||
}
|
||||
input.classList.remove("is-invalid");
|
||||
|
||||
sendBtn.disabled = true;
|
||||
sendBtn.textContent = "Sende...";
|
||||
|
||||
try {
|
||||
const res = await fetch("http://localhost:3000/api/invitation", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Username": window.currentUser
|
||||
},
|
||||
body: JSON.stringify({
|
||||
toUser,
|
||||
eventId: event.id,
|
||||
eventName: event.name
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
// Fehlermeldung vom Server auslesen (z.B. "Nutzer nicht registriert")
|
||||
const errData = await res.json().catch(() => ({}));
|
||||
throw new Error(errData.message || `Fehler ${res.status}`);
|
||||
}
|
||||
|
||||
// Formular entfernen und Erfolgsmeldung anzeigen
|
||||
form.remove();
|
||||
showCardMessage(card, `Einladung an "${toUser}" gesendet.`, "success");
|
||||
|
||||
} catch (err) {
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.textContent = "Senden";
|
||||
showCardMessage(card, err.message || "Fehler beim Senden der Einladung.", "danger");
|
||||
console.error("Einladungs-Fehler:", err);
|
||||
}
|
||||
});
|
||||
|
||||
form.append(input, sendBtn, cancelBtn);
|
||||
return form;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// HILFSFUNKTION – Status-Nachricht in Karte anzeigen
|
||||
// Zeigt eine kurze Meldung innerhalb der Karte an und blendet sie nach 3s aus
|
||||
// =========================
|
||||
function showCardMessage(card, message, type) {
|
||||
// Alte Nachrichten entfernen
|
||||
card.querySelectorAll(".card-message").forEach(el => el.remove());
|
||||
|
||||
const msg = document.createElement("p");
|
||||
msg.className = `card-message text-${type} mt-1 mb-0`;
|
||||
msg.textContent = message;
|
||||
card.appendChild(msg);
|
||||
|
||||
setTimeout(() => msg.remove(), 3000);
|
||||
}
|
||||
|
||||
// =========================
|
||||
// HILFSFUNKTION – Prüft ob Event bereits gespeichert ist
|
||||
// =========================
|
||||
function isAlreadySaved(eventId) {
|
||||
const saved = JSON.parse(localStorage.getItem("savedEvents") || "[]");
|
||||
return saved.some(e => e.id === eventId);
|
||||
}
|
||||
|
||||
@ -1,11 +1,38 @@
|
||||
/**
|
||||
* eventList.js – Rendert eine Liste von Event-Karten in einen Container.
|
||||
* Delegiert die Erstellung einzelner Karten an createEventCard().
|
||||
*/
|
||||
|
||||
import { createEventCard } from "./eventCard.js";
|
||||
|
||||
/**
|
||||
* Leert den Container und fügt für jedes Event eine Karte ein.
|
||||
* Zeigt eine Meldung an, wenn keine Events gefunden wurden.
|
||||
* @param {Array} events - Array von Event-Objekten
|
||||
* @param {HTMLElement} container - Ziel-DOM-Element
|
||||
*/
|
||||
export function renderEventList(events, container) {
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
if (events.length === 0) {
|
||||
container.innerHTML = "No events found.";
|
||||
const state = document.createElement("div");
|
||||
state.className = "empty-state";
|
||||
|
||||
const icon = document.createElement("div");
|
||||
icon.className = "empty-state__icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent = "🎭";
|
||||
|
||||
const title = document.createElement("p");
|
||||
title.className = "empty-state__text";
|
||||
title.textContent = "Keine Events gefunden";
|
||||
|
||||
const sub = document.createElement("p");
|
||||
sub.className = "empty-state__sub";
|
||||
sub.textContent = "Versuche eine andere Stadt oder erweitere den Datumsbereich.";
|
||||
|
||||
state.append(icon, title, sub);
|
||||
container.appendChild(state);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -13,5 +40,4 @@ export function renderEventList(events, container) {
|
||||
const card = createEventCard(event);
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
254
server/server.js
254
server/server.js
@ -1,167 +1,209 @@
|
||||
/**
|
||||
* server.js – Express-Backend für Encore.
|
||||
*
|
||||
* Funktionen:
|
||||
* - Proxy für die Ticketmaster Discovery API (API-Key bleibt serverseitig)
|
||||
* - Einfache Benutzerverwaltung (In-Memory, kein Persistenz-Layer)
|
||||
* - Einladungssystem zwischen registrierten Benutzern
|
||||
*
|
||||
* Sicherheitshinweise (OWASP):
|
||||
* - A02 Cryptographic Failures: Passwörter werden nicht gehasht (bewusste Vereinfachung)
|
||||
* - A07 Identification and Authentication Failures: Keine Tokens/Sessions, nur Header-Auth
|
||||
* - Der API-Key liegt serverseitig in .env und wird nie an den Client übermittelt
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import dotenv from "dotenv";
|
||||
import cors from "cors";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
// Middleware: CORS erlaubt Anfragen vom Frontend (localhost), JSON-Body parsen
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// =========================
|
||||
// ROOT
|
||||
// ROOT – Health-Check
|
||||
// =========================
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Encore API is running");
|
||||
});
|
||||
|
||||
// =========================
|
||||
// TICKETMASTER EVENTS (PROXY)
|
||||
// TICKETMASTER-PROXY
|
||||
// Versteckt den API-Key vor dem Client (A02: Sensitive Data Exposure vermeiden)
|
||||
// GET /api/events?city={city}
|
||||
// =========================
|
||||
const TM_API_KEY = process.env.TM_API_KEY;
|
||||
|
||||
app.get("/api/events", async (req, res) => {
|
||||
|
||||
const city = req.query.city;
|
||||
|
||||
if (!city) {
|
||||
return res.status(400).json({ message: "City is required" });
|
||||
return res.status(400).json({ message: "Stadt ist erforderlich" });
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `https://app.ticketmaster.com/discovery/v2/events.json?apikey=${TM_API_KEY}&city=${city}`;
|
||||
|
||||
const url = `https://app.ticketmaster.com/discovery/v2/events.json?apikey=${TM_API_KEY}&city=${encodeURIComponent(city)}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
const data = await response.json();
|
||||
|
||||
res.json(data);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Event fetch error:", error);
|
||||
res.status(500).json({ message: "Error fetching events" });
|
||||
console.error("Ticketmaster-Fehler:", error);
|
||||
res.status(500).json({ message: "Fehler beim Laden der Events" });
|
||||
}
|
||||
});
|
||||
|
||||
// =========================
|
||||
// INVITATIONS SYSTEM
|
||||
// BENUTZERVERWALTUNG
|
||||
// In-Memory-Speicher (Daten gehen bei Serverneustart verloren – bewusste Vereinfachung)
|
||||
// =========================
|
||||
let invitations = [];
|
||||
let idCounter = 1;
|
||||
|
||||
// SEND INVITATION
|
||||
app.post("/api/invitation", (req, res) => {
|
||||
|
||||
if (!req.body) {
|
||||
return res.status(400).json({ message: "Missing request body" });
|
||||
}
|
||||
|
||||
const fromUser = req.header("X-Username");
|
||||
const { toUser, eventId, eventName } = req.body;
|
||||
|
||||
if (!fromUser || !toUser) {
|
||||
return res.status(400).json({ message: "Missing users" });
|
||||
}
|
||||
|
||||
const invitation = {
|
||||
id: idCounter++,
|
||||
fromUser,
|
||||
toUser,
|
||||
eventId,
|
||||
eventName,
|
||||
status: "pending"
|
||||
};
|
||||
|
||||
invitations.push(invitation);
|
||||
|
||||
res.json(invitation);
|
||||
});
|
||||
|
||||
// GET INVITATIONS FOR USER
|
||||
app.get("/api/invitation", (req, res) => {
|
||||
|
||||
const user = req.header("X-Username");
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: "Missing username" });
|
||||
}
|
||||
|
||||
const userInvitations = invitations.filter(
|
||||
inv => inv.toUser === user
|
||||
);
|
||||
|
||||
res.json(userInvitations);
|
||||
});
|
||||
|
||||
// ACCEPT INVITATION
|
||||
app.post("/api/invitation/:id/accept", (req, res) => {
|
||||
|
||||
const inv = invitations.find(i => i.id == req.params.id);
|
||||
|
||||
if (!inv) {
|
||||
return res.status(404).json({ message: "Invitation not found" });
|
||||
}
|
||||
|
||||
inv.status = "accepted";
|
||||
|
||||
res.json(inv);
|
||||
});
|
||||
|
||||
// DECLINE INVITATION
|
||||
app.post("/api/invitation/:id/decline", (req, res) => {
|
||||
|
||||
const inv = invitations.find(i => i.id == req.params.id);
|
||||
|
||||
if (!inv) {
|
||||
return res.status(404).json({ message: "Invitation not found" });
|
||||
}
|
||||
|
||||
inv.status = "declined";
|
||||
|
||||
res.json(inv);
|
||||
});
|
||||
|
||||
let users = [];
|
||||
|
||||
// CREATE USER
|
||||
/**
|
||||
* POST /api/user – Neuen Benutzer registrieren.
|
||||
* Vergibt ein Standard-Passwort "1234" (vereinfacht, kein Hashing).
|
||||
* Antwortet mit 201 Created bei Erfolg, 400 bei fehlendem/doppeltem Benutzernamen.
|
||||
*/
|
||||
app.post("/api/user", (req, res) => {
|
||||
|
||||
const { username } = req.body;
|
||||
|
||||
if (!username) {
|
||||
return res.status(400).json({ message: "Username required" });
|
||||
return res.status(400).json({ message: "Benutzername erforderlich" });
|
||||
}
|
||||
|
||||
if (users.find(u => u.username === username)) {
|
||||
return res.status(400).json({ message: "User exists" });
|
||||
return res.status(400).json({ message: "Benutzer existiert bereits" });
|
||||
}
|
||||
|
||||
const password = "1234";
|
||||
users.push({ username, password });
|
||||
|
||||
const user = { username, password };
|
||||
users.push(user);
|
||||
|
||||
res.json({ name: username, password });
|
||||
res.status(201).json({ name: username, password });
|
||||
});
|
||||
|
||||
// LOGIN
|
||||
/**
|
||||
* GET /api/user – Benutzer anmelden.
|
||||
* Credentials kommen als X-Username und X-Password Header.
|
||||
* Antwortet mit 200 OK bei Erfolg, 401 Unauthorized bei falschen Credentials.
|
||||
*
|
||||
* Hinweis: GET für eine Login-Operation ist unkonventionell (normalerweise POST).
|
||||
* Hier bewusst so gewählt, da kein Session/Token-Mechanismus implementiert ist.
|
||||
*/
|
||||
app.get("/api/user", (req, res) => {
|
||||
|
||||
const username = req.header("X-Username");
|
||||
const password = req.header("X-Password");
|
||||
|
||||
const user = users.find(
|
||||
u => u.username === username && u.password === password
|
||||
);
|
||||
const user = users.find(u => u.username === username && u.password === password);
|
||||
|
||||
if (!user) return res.sendStatus(401);
|
||||
|
||||
res.sendStatus(201);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
// =========================
|
||||
// START SERVER
|
||||
// EINLADUNGSSYSTEM
|
||||
// Verwaltet Event-Einladungen zwischen Benutzern
|
||||
// =========================
|
||||
let invitations = [];
|
||||
let idCounter = 1;
|
||||
|
||||
/**
|
||||
* POST /api/invitation – Einladung senden.
|
||||
* Erwartet: Header X-Username (Sender), Body { toUser, eventId, eventName }
|
||||
* Antwortet mit 201 Created und dem erstellten Einladungsobjekt.
|
||||
*/
|
||||
app.post("/api/invitation", (req, res) => {
|
||||
if (!req.body) {
|
||||
return res.status(400).json({ message: "Request-Body fehlt" });
|
||||
}
|
||||
|
||||
const fromUser = req.header("X-Username");
|
||||
const { toUser, eventId, eventName } = req.body;
|
||||
|
||||
if (!fromUser || !toUser) {
|
||||
return res.status(400).json({ message: "Sender und Empfänger sind erforderlich" });
|
||||
}
|
||||
|
||||
// Prüfen ob der Empfänger registriert ist
|
||||
if (!users.find(u => u.username === toUser)) {
|
||||
return res.status(404).json({ message: `Nutzer "${toUser}" ist nicht registriert.` });
|
||||
}
|
||||
|
||||
// Nicht sich selbst einladen
|
||||
if (fromUser === toUser) {
|
||||
return res.status(400).json({ message: "Du kannst dich nicht selbst einladen." });
|
||||
}
|
||||
|
||||
const invitation = {
|
||||
id: idCounter++,
|
||||
fromUser,
|
||||
toUser,
|
||||
eventId,
|
||||
eventName,
|
||||
status: "pending"
|
||||
};
|
||||
|
||||
invitations.push(invitation);
|
||||
res.status(201).json(invitation);
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/invitation – Alle Einladungen für einen Benutzer abrufen.
|
||||
* Erwartet: Header X-Username (Empfänger)
|
||||
* Antwortet mit 200 und Array der Einladungen.
|
||||
*/
|
||||
app.get("/api/invitation", (req, res) => {
|
||||
const user = req.header("X-Username");
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: "Benutzername fehlt" });
|
||||
}
|
||||
|
||||
const userInvitations = invitations.filter(inv => inv.toUser === user);
|
||||
res.json(userInvitations);
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /api/invitation/:id – Einladungsstatus aktualisieren (annehmen oder ablehnen).
|
||||
* Erwartet: Body { status: "accepted" | "declined" }
|
||||
* HTTP-Verb PUT gemäss REST: Update einer bestehenden Ressource.
|
||||
* Antwortet mit 200 OK und dem aktualisierten Einladungsobjekt.
|
||||
*/
|
||||
app.put("/api/invitation/:id", (req, res) => {
|
||||
const inv = invitations.find(i => i.id == req.params.id);
|
||||
|
||||
if (!inv) return res.status(404).json({ message: "Einladung nicht gefunden" });
|
||||
|
||||
const { status } = req.body;
|
||||
if (!["accepted", "declined"].includes(status)) {
|
||||
return res.status(400).json({ message: "Ungültiger Status. Erlaubt: accepted, declined" });
|
||||
}
|
||||
|
||||
inv.status = status;
|
||||
res.json(inv);
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/invitation/:id – Einladung löschen.
|
||||
* HTTP-Verb DELETE gemäss REST: Entfernt die Ressource dauerhaft.
|
||||
* Antwortet mit 204 No Content bei Erfolg.
|
||||
*/
|
||||
app.delete("/api/invitation/:id", (req, res) => {
|
||||
const index = invitations.findIndex(i => i.id == req.params.id);
|
||||
|
||||
if (index === -1) return res.status(404).json({ message: "Einladung nicht gefunden" });
|
||||
|
||||
invitations.splice(index, 1);
|
||||
res.sendStatus(204); // 204 No Content – Ressource erfolgreich gelöscht
|
||||
});
|
||||
|
||||
// =========================
|
||||
// SERVER STARTEN
|
||||
// =========================
|
||||
app.listen(3000, () => {
|
||||
console.log("Server running on http://localhost:3000");
|
||||
});
|
||||
console.log("Server läuft auf http://localhost:3000");
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user