From dd492fd69e36d2f7e7deb938f809f9d54c6312b2 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 17 Oct 2025 23:31:14 +0200 Subject: [PATCH] Add roles routes to backend and frontend; implement routing and UI components for admin roles management --- backend/src/controllers/RolesController.js | 58 +++++ backend/src/index.js | 4 + backend/src/routes/roles.js | 17 ++ backend/src/services/RolesService.js | 91 +++++++ frontend/src/App.vue | 1 + frontend/src/router/index.js | 7 + frontend/src/views/Roles.vue | 280 +++++++++++++++++++++ 7 files changed, 458 insertions(+) create mode 100644 backend/src/controllers/RolesController.js create mode 100644 backend/src/routes/roles.js create mode 100644 backend/src/services/RolesService.js create mode 100644 frontend/src/views/Roles.vue diff --git a/backend/src/controllers/RolesController.js b/backend/src/controllers/RolesController.js new file mode 100644 index 0000000..65217d1 --- /dev/null +++ b/backend/src/controllers/RolesController.js @@ -0,0 +1,58 @@ +const RolesService = require('../services/RolesService'); + +/** + * Controller für Rollen-Verwaltung + * Verarbeitet HTTP-Requests und delegiert an RolesService + */ +class RolesController { + /** + * Holt alle Benutzer mit ihren Rollen + */ + async getAllUsers(req, res) { + try { + const users = await RolesService.getAllUsers(); + res.json(users); + } catch (error) { + console.error('Fehler beim Abrufen der Benutzer:', error); + res.status(500).json({ + message: 'Fehler beim Abrufen der Benutzer', + error: error.message + }); + } + } + + /** + * Ändert die Rolle eines Benutzers + */ + async updateUserRole(req, res) { + try { + const adminUserId = req.user?.id || 1; + const targetUserId = parseInt(req.params.id); + const { role } = req.body; + + if (isNaN(targetUserId)) { + return res.status(400).json({ message: 'Ungültige Benutzer-ID' }); + } + + if (role === undefined || role === null) { + return res.status(400).json({ message: 'Rolle ist erforderlich' }); + } + + const user = await RolesService.updateUserRole(adminUserId, targetUserId, role); + res.json(user); + } catch (error) { + console.error('Fehler beim Aktualisieren der Rolle:', error); + + if (error.message.includes('Keine Berechtigung')) { + return res.status(403).json({ message: error.message }); + } + + res.status(500).json({ + message: error.message + }); + } + } +} + +module.exports = new RolesController(); + diff --git a/backend/src/index.js b/backend/src/index.js index 1d9b598..63ab12b 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -102,6 +102,10 @@ app.use('/api/password', authenticateToken, passwordRouter); const timewishRouter = require('./routes/timewish'); app.use('/api/timewish', authenticateToken, timewishRouter); +// Roles routes (geschützt, nur Admin) - MIT ID-Hashing +const rolesRouter = require('./routes/roles'); +app.use('/api/roles', authenticateToken, rolesRouter); + // Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); diff --git a/backend/src/routes/roles.js b/backend/src/routes/roles.js new file mode 100644 index 0000000..55f2656 --- /dev/null +++ b/backend/src/routes/roles.js @@ -0,0 +1,17 @@ +const express = require('express'); +const router = express.Router(); +const RolesController = require('../controllers/RolesController'); +const unhashRequestIds = require('../middleware/unhashRequest'); + +/** + * Routen für Rollen-Verwaltung (nur für Admins) + */ + +// GET /api/roles/users - Alle Benutzer mit Rollen abrufen +router.get('/users', RolesController.getAllUsers.bind(RolesController)); + +// PUT /api/roles/users/:id - Rolle eines Benutzers ändern +router.put('/users/:id', unhashRequestIds, RolesController.updateUserRole.bind(RolesController)); + +module.exports = router; + diff --git a/backend/src/services/RolesService.js b/backend/src/services/RolesService.js new file mode 100644 index 0000000..3a1009a --- /dev/null +++ b/backend/src/services/RolesService.js @@ -0,0 +1,91 @@ +const database = require('../config/database'); + +/** + * Service-Klasse für Rollen-Verwaltung + * Nur für Admins: Verwaltet User-Rollen + */ +class RolesService { + /** + * Holt alle Benutzer mit ihren Rollen + * @returns {Promise} Array von Benutzern + */ + async getAllUsers() { + const { User, State } = database.getModels(); + + const users = await User.findAll({ + include: [ + { + model: State, + as: 'state', + attributes: ['state_name'], + required: false + } + ], + order: [['full_name', 'ASC']] + }); + + return users.map(u => ({ + id: u.id, + fullName: u.full_name, + role: u.role, // 0 = user, 1 = admin + roleString: u.role === 1 ? 'admin' : 'user', + stateName: u.state?.state_name || null + })); + } + + /** + * Ändert die Rolle eines Benutzers + * @param {number} adminUserId - ID des Admins, der die Änderung vornimmt + * @param {number} targetUserId - ID des zu ändernden Users + * @param {number} newRole - Neue Rolle (0 = user, 1 = admin) + * @returns {Promise} Aktualisierter User + */ + async updateUserRole(adminUserId, targetUserId, newRole) { + const { User } = database.getModels(); + + // Prüfe ob Admin + const admin = await User.findByPk(adminUserId); + if (!admin || admin.role !== 1) { + throw new Error('Keine Berechtigung für diese Aktion'); + } + + // Validierung + if (newRole !== 0 && newRole !== 1) { + throw new Error('Ungültige Rolle (muss 0 oder 1 sein)'); + } + + // Hole Ziel-User + const targetUser = await User.findByPk(targetUserId); + + if (!targetUser) { + throw new Error('Benutzer nicht gefunden'); + } + + // Verhindere, dass der einzige Admin sich selbst degradiert + if (targetUserId === adminUserId && newRole === 0) { + const adminCount = await User.count({ + where: { role: 1 } + }); + + if (adminCount <= 1) { + throw new Error('Sie können sich nicht selbst degradieren, wenn Sie der einzige Admin sind'); + } + } + + // Aktualisiere Rolle + targetUser.role = newRole; + await targetUser.save(); + + console.log(`Admin ${adminUserId} hat User ${targetUserId} Rolle auf ${newRole} geändert`); + + return { + id: targetUser.id, + fullName: targetUser.full_name, + role: targetUser.role, + roleString: targetUser.role === 1 ? 'admin' : 'user' + }; + } +} + +module.exports = new RolesService(); + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 4cfef9d..bff6449 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -73,6 +73,7 @@ const pageTitle = computed(() => { 'workdays': 'Arbeitstage', 'calendar': 'Kalender', 'admin-holidays': 'Feiertage', + 'admin-roles': 'Rechte', 'settings-profile': 'Persönliches', 'settings-password': 'Passwort ändern', 'settings-timewish': 'Zeitwünsche', diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index a4dc582..3169285 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -19,6 +19,7 @@ import Holidays from '../views/Holidays.vue' import Profile from '../views/Profile.vue' import PasswordChange from '../views/PasswordChange.vue' import Timewish from '../views/Timewish.vue' +import Roles from '../views/Roles.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -101,6 +102,12 @@ const router = createRouter({ component: Holidays, meta: { requiresAuth: true, requiresAdmin: true } }, + { + path: '/admin/roles', + name: 'admin-roles', + component: Roles, + meta: { requiresAuth: true, requiresAdmin: true } + }, { path: '/settings/profile', name: 'settings-profile', diff --git a/frontend/src/views/Roles.vue b/frontend/src/views/Roles.vue new file mode 100644 index 0000000..88f0afc --- /dev/null +++ b/frontend/src/views/Roles.vue @@ -0,0 +1,280 @@ + + + + + +