Compare commits

...

10 Commits

Author SHA1 Message Date
7345878f10 filter
All checks were successful
Build & Push Image / build-image (push) Successful in 1m29s
2026-03-02 19:05:53 +09:00
5569f35037 elemente statt todos in dashboard
All checks were successful
Build & Push Image / build-image (push) Successful in 1m27s
2026-03-02 18:57:22 +09:00
8965ffc11b dashboard war nur kurz zu sehen
All checks were successful
Build & Push Image / build-image (push) Successful in 1m2s
2026-03-02 18:50:23 +09:00
aa0c82e6d3 register-seite erstellt
All checks were successful
Build & Push Image / build-image (push) Successful in 2m41s
2026-03-02 18:01:42 +09:00
Ali
0a488a80ce auth
All checks were successful
Build & Push Image / build-image (push) Successful in 1m20s
2026-02-24 21:57:25 +09:00
Ali
a12b65a488 auth
All checks were successful
Build & Push Image / build-image (push) Successful in 1m50s
2026-02-24 21:50:24 +09:00
Ali
1d539e44ac auth
Some checks failed
Build & Push Image / build-image (push) Has been cancelled
2026-02-24 21:49:23 +09:00
Ali
9ade18903c auth
All checks were successful
Build & Push Image / build-image (push) Successful in 2m27s
2026-02-24 21:37:48 +09:00
Ali
4acd9d0245 auth
All checks were successful
Build & Push Image / build-image (push) Successful in 1m45s
2026-02-24 21:18:59 +09:00
Ali
f9c8fde7f3 auth
All checks were successful
Build & Push Image / build-image (push) Successful in 1m10s
2026-02-24 21:16:06 +09:00
10 changed files with 300 additions and 50 deletions

20
public/api.js Normal file
View File

@@ -0,0 +1,20 @@
export async function authFetch(url, options = {}) {
const token = localStorage.getItem("token"); // hol den JWT aus localStorage
// füge Headers zusammen: ContentType + Bearer Token
const headers = {
"Content-Type": "application/json",
...options.headers, // vorhandene Header nicht überschreiben
...(token ? {
"Authorization": `Bearer ${token}` // 🔑 wichtiger Header
} : {})
};
// führe den fetch aus mit dem zusammengebauten Header
const response = await fetch(url, {
...options,
headers
});
return response; // response weitergeben
}

View File

@@ -2,29 +2,46 @@
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Dashboard</title> <title>Dashboard - Schic App</title>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Dashboard</h1> <h1>Dashboard</h1>
<p>Login erfolgreich 🎉</p> <p id="welcomeMessage">Willkommen …</p>
<button id="logout">Logout</button>
<button id="logoutBtn">Logout</button>
<h2>Elemente</h2>
<div class="elements-container">
<div class="filters">
<label>Inhalt: <input type="text" id="filter-inhalt" placeholder="Filter Inhalt"></label>
<label>Bundesland: <input type="text" id="filter-bundesland" placeholder="Filter Bundesland"></label>
<label>Fach: <input type="text" id="filter-fach" placeholder="Filter Fach"></label>
<label>Version: <input type="text" id="filter-version" placeholder="Filter Version"></label>
<label>Stufe: <input type="number" id="filter-stufe" placeholder="Stufe"></label>
<button id="clear-filters">Filter zurücksetzen</button>
</div> </div>
<script> <table id="elementsTable">
const token = localStorage.getItem("token") <thead>
<tr>
<th>Inhalt</th>
<th>Bundesland</th>
<th>Fach</th>
<th>Version</th>
<th>Stufe</th>
</tr>
</thead>
<tbody id="elementsTbody"></tbody>
</table>
</div>
if (!token) { <p id="errorMessage" class="error"></p>
window.location.href = "login.html" </div>
}
document.getElementById("logout").addEventListener("click", () => {
localStorage.removeItem("token")
window.location.href = "login.html"
})
</script>
<!-- Lade das DashboardScript -->
<script type="module" src="dashboard.js"></script>
</body> </body>
</html> </html>

157
public/dashboard.js Normal file
View File

@@ -0,0 +1,157 @@
// dashboard.js
import { authFetch } from "./api.js";
// ElementReferenzen
const welcomeMessage = document.getElementById("welcomeMessage");
const todosList = document.getElementById("todosList");
const errorMessage = document.getElementById("errorMessage");
const logoutBtn = document.getElementById("logoutBtn");
// Elements UI
const elementsTbody = document.getElementById("elementsTbody");
const filterInhalt = document.getElementById("filter-inhalt");
const filterBundesland = document.getElementById("filter-bundesland");
const filterFach = document.getElementById("filter-fach");
const filterVersion = document.getElementById("filter-version");
const filterStufe = document.getElementById("filter-stufe");
const clearFiltersBtn = document.getElementById("clear-filters");
// Prüfe ob ein JWT vorhanden ist
const token = localStorage.getItem("token");
if (!token) {
// Kein Token → zurück zur LoginSeite
window.location.href = "login.html";
}
// Logout Token löschen + weiterleiten
logoutBtn.addEventListener("click", () => {
localStorage.removeItem("token");
window.location.href = "login.html";
});
// Lade Todos vom Backend
async function loadElements() {
try {
const res = await authFetch("/elements"); // fetchWrapper sendet den JWT Automatisch
if (!res.ok) {
errorMessage.textContent = "Fehler beim Abrufen der Elemente!";
if (res.status === 401) {
window.location.href = "login.html"; // bei ungültigem Token zum Login
}
return;
}
const todos = await res.json();
// Zeige Todos in der Liste an
todosList.innerHTML = "";
if (Array.isArray(todos) && todos.length > 0) {
todos.forEach(todo => {
const li = document.createElement("li");
li.textContent = todo.task;
todosList.appendChild(li);
});
} else {
todosList.innerHTML = "<li>Keine Todos gefunden.</li>";
}
} catch (err) {
console.error("Fetch Error:", err);
errorMessage.textContent = "Serverfehler beim Laden!";
}
}
// Willkommenstext setzen
welcomeMessage.textContent = "Willkommen zum Dashboard!";
// Rufe die Todos ab
loadTodos();
// ---------- Elements: load, render, filter ----------
let elementsData = [];
async function loadElements() {
try {
// Route: mounted at /elements + route '/elements' => /elements/elements
const res = await authFetch('/elements/elements');
if (!res.ok) {
errorMessage.textContent = 'Fehler beim Abrufen der Elemente.';
return;
}
elementsData = await res.json();
renderElements(elementsData);
} catch (err) {
console.error('Elements Fetch Error:', err);
errorMessage.textContent = 'Serverfehler beim Laden der Elemente.';
}
}
function renderElements(data) {
if (!elementsTbody) return;
elementsTbody.innerHTML = '';
if (!Array.isArray(data) || data.length === 0) {
const tr = document.createElement('tr');
const td = document.createElement('td');
td.colSpan = 5;
td.textContent = 'Keine Elemente gefunden.';
tr.appendChild(td);
elementsTbody.appendChild(tr);
return;
}
data.forEach(el => {
const tr = document.createElement('tr');
// Skip id; show columns in schema order
const cols = [el.inhalt, el.bundesland, el.fach, el.version, el.stufe];
cols.forEach(val => {
const td = document.createElement('td');
td.textContent = (val === null || val === undefined) ? '' : String(val);
tr.appendChild(td);
});
elementsTbody.appendChild(tr);
});
}
function applyFilters() {
if (!elementsData || elementsData.length === 0) return renderElements([]);
const fInhalt = (filterInhalt?.value || '').trim().toLowerCase();
const fBund = (filterBundesland?.value || '').trim().toLowerCase();
const fFach = (filterFach?.value || '').trim().toLowerCase();
const fVer = (filterVersion?.value || '').trim().toLowerCase();
const fSt = (filterStufe?.value || '').trim();
const filtered = elementsData.filter(el => {
if (fInhalt && !(el.inhalt || '').toLowerCase().includes(fInhalt)) return false;
if (fBund && !(el.bundesland || '').toLowerCase().includes(fBund)) return false;
if (fFach && !(el.fach || '').toLowerCase().includes(fFach)) return false;
if (fVer && !(el.version || '').toLowerCase().includes(fVer)) return false;
if (fSt) {
const n = Number(fSt);
if (!Number.isNaN(n)) {
if (Number(el.stufe) !== n) return false;
} else {
// non-numeric filter: substring match
if (!(String(el.stufe) || '').includes(fSt)) return false;
}
}
return true;
});
renderElements(filtered);
}
function setupFilterListeners() {
const inputs = [filterInhalt, filterBundesland, filterFach, filterVersion, filterStufe];
inputs.forEach(i => { if (i) i.addEventListener('input', applyFilters); });
if (clearFiltersBtn) clearFiltersBtn.addEventListener('click', () => {
inputs.forEach(i => { if (i) i.value = ''; });
applyFilters();
});
}
// Initialize elements UI if present
if (elementsTbody) {
setupFilterListeners();
loadElements();
}

View File

@@ -19,6 +19,7 @@
<p id="errorMessage" class="error"></p> <p id="errorMessage" class="error"></p>
<a href="index.html">Zurück zur Startseite</a> <a href="index.html">Zurück zur Startseite</a>
<a href="register.html">Noch keinen Account? Registrieren</a>
</div> </div>
<script src="login.js"></script> <script src="login.js"></script>

View File

@@ -1,40 +1,31 @@
const form = document.getElementById("loginForm") const form = document.getElementById("loginForm");
const errorMessage = document.getElementById("errorMessage") const errorMessage = document.getElementById("errorMessage");
if (form) {
console.log("LoginForm gefunden")
} else {
console.log("LoginForm NICHT gefunden")
}
form.addEventListener("submit", async (event) => { form.addEventListener("submit", async (event) => {
event.preventDefault() event.preventDefault();
const username = document.getElementById("username").value const username = document.getElementById("username").value;
const password = document.getElementById("password").value const password = document.getElementById("password").value;
try { try {
const response = await fetch("/auth/login", { const response = await fetch("/auth/login", {
method: "POST", method: "POST",
headers: { headers: { "Content-Type": "application/json" },
"Content-Type": "application/json"
},
body: JSON.stringify({ username, password }) body: JSON.stringify({ username, password })
}) });
const data = await response.json() const data = await response.json();
if (!response.ok) { if (!response.ok) {
errorMessage.textContent = data.message errorMessage.textContent = data?.message || "Login fehlgeschlagen";
return return;
} }
// Token speichern localStorage.setItem("token", data.token);
localStorage.setItem("token", data.token) window.location.href = "dashboard.html";
// Weiterleiten
window.location.href = "dashboard.html"
} catch (err) { } catch (err) {
errorMessage.textContent = "Server nicht erreichbar" console.error("Login Error:", err);
errorMessage.textContent = "Server nicht erreichbar";
} }
}) });

27
public/register.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Login - Schic-App</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Registrieren</h1>
<form id="registerForm">
<input type="text" id="username" placeholder="Username" required>
<input type="password" id="password" placeholder="Passwort" required>
<button type="submit">Registrieren</button>
</form>
<p id="errorMessage" class="error"></p>
<a href="index.html">Zurück zur Startseite</a>
<a href="login.html">Bereits registriert? Einloggen</a>
</div>
<script src="register.js"></script>
</body>
</html>

35
public/register.js Normal file
View File

@@ -0,0 +1,35 @@
const form = document.getElementById("registerForm");
const errorMessage = document.getElementById("errorMessage");
if (!form) {
console.error('Register form not found in DOM');
} else {
form.addEventListener("submit", async (event) => {
event.preventDefault();
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
try {
const response = await fetch("/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (!response.ok) {
errorMessage.textContent = data?.message || "Registrierung fehlgeschlagen";
return;
}
localStorage.setItem("token", data.token);
window.location.href = "dashboard.html";
} catch (err) {
console.error("Register Error:", err);
errorMessage.textContent = "Server nicht erreichbar";
}
})
}

View File

@@ -1,12 +1,14 @@
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
function authMiddleware(req, res, next) { function authMiddleware(req, res, next) {
const token = req.headers['authorization'] const authHeader = req.headers['authorization'] || req.headers['Authorization'];
if (!authHeader) { return res.status(401).json({ message: "No token provided" }) }
if (!token) { return res.status(401).json({ message: "No token provided" }) } // support headers in the form: 'Bearer <token>' or just the token
const token = authHeader.startsWith('Bearer ') ? authHeader.split(' ')[1] : authHeader;
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if(err) {return res.status(401).json({message: "Invalid token"})} if (err) { return res.status(401).json({ message: "Invalid token" }) }
req.userId = decoded.id req.userId = decoded.id
next() next()
}) })

View File

@@ -42,7 +42,7 @@ router.post('/login', async (req, res) => {
const { username, password } = req.body const { username, password } = req.body
try { try {
const user = await prisma.users.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
username: username username: username
} }
@@ -59,9 +59,9 @@ router.post('/login', async (req, res) => {
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '24h' }) const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '24h' })
res.json({ token }) res.json({ token })
} catch (err) { } catch (err) {
console.log(err.message) console.error("LOGIN ROUTE ERROR:", err);
res.sendStatus(503) res.status(500).json({ message: "Server error during login" });
} }
}) })

View File

@@ -3,7 +3,7 @@ import prisma from '../prismaClient.js'
const router = express.Router() const router = express.Router()
router.get('/', async (req, res) => { router.get('/elements', async (req, res) => {
try { try {
// Versuche es mit exakt dem Namen aus deinem Schema (Elements) // Versuche es mit exakt dem Namen aus deinem Schema (Elements)
const data = await prisma.Elements.findMany(); const data = await prisma.Elements.findMany();