Änderung: Erweiterung der Benutzer- und Rechteverwaltung im Admin-Bereich
Änderungen: - Neue Funktionen zur Benutzerverwaltung hinzugefügt: Benutzer suchen, Benutzer abrufen und Benutzer aktualisieren. - Implementierung von Funktionen zur Verwaltung von Benutzerrechten: Rechtearten auflisten, Benutzerrechte auflisten, Recht hinzufügen und Recht entfernen. - Routen für die neuen Funktionen im Admin-Router definiert. - Übersetzungen für Benutzer- und Rechteverwaltung in den Sprachdateien aktualisiert. Diese Anpassungen verbessern die Verwaltung von Benutzern und deren Rechten im Admin-Bereich und erweitern die Funktionalität der Anwendung.
This commit is contained in:
@@ -23,6 +23,17 @@ class AdminController {
|
||||
this.getRooms = this.getRooms.bind(this);
|
||||
this.createRoom = this.createRoom.bind(this);
|
||||
this.deleteRoom = this.deleteRoom.bind(this);
|
||||
|
||||
// User administration
|
||||
this.searchUsers = this.searchUsers.bind(this);
|
||||
this.getUser = this.getUser.bind(this);
|
||||
this.updateUser = this.updateUser.bind(this);
|
||||
|
||||
// Rights
|
||||
this.listRightTypes = this.listRightTypes.bind(this);
|
||||
this.listUserRights = this.listUserRights.bind(this);
|
||||
this.addUserRight = this.addUserRight.bind(this);
|
||||
this.removeUserRight = this.removeUserRight.bind(this);
|
||||
}
|
||||
|
||||
async getOpenInterests(req, res) {
|
||||
@@ -35,6 +46,93 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
// --- User Administration ---
|
||||
async searchUsers(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { q } = req.query;
|
||||
const result = await AdminService.searchUsers(requester, q || '');
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const result = await AdminService.getUserByHashedId(requester, id);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' ? 404 : 500);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const result = await AdminService.updateUser(requester, id, req.body || {});
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' ? 404 : 500);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
// --- Rights ---
|
||||
async listRightTypes(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const types = await AdminService.listUserRightTypes(requester);
|
||||
res.status(200).json(types);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async listUserRights(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const rights = await AdminService.listUserRightsForUser(requester, id);
|
||||
res.status(200).json(rights);
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' ? 404 : 500);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async addUserRight(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const { rightTypeId } = req.body || {};
|
||||
const result = await AdminService.addUserRight(requester, id, rightTypeId);
|
||||
res.status(201).json({ status: 'ok' });
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' || error.message === 'wrongtype' ? 404 : 500);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async removeUserRight(req, res) {
|
||||
try {
|
||||
const { userid: requester } = req.headers;
|
||||
const { id } = req.params;
|
||||
const { rightTypeId } = req.body || {};
|
||||
await AdminService.removeUserRight(requester, id, rightTypeId);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const status = error.message === 'noaccess' ? 403 : (error.message === 'notfound' ? 404 : 500);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async changeInterest(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
|
||||
@@ -15,6 +15,17 @@ router.post('/chat/rooms', authenticate, adminController.createRoom);
|
||||
router.put('/chat/rooms/:id', authenticate, adminController.updateRoom);
|
||||
router.delete('/chat/rooms/:id', authenticate, adminController.deleteRoom);
|
||||
|
||||
// --- Users Admin ---
|
||||
router.get('/users/search', authenticate, adminController.searchUsers);
|
||||
router.get('/users/:id', authenticate, adminController.getUser);
|
||||
router.put('/users/:id', authenticate, adminController.updateUser);
|
||||
|
||||
// --- Rights Admin ---
|
||||
router.get('/rights/types', authenticate, adminController.listRightTypes);
|
||||
router.get('/rights/:id', authenticate, adminController.listUserRights);
|
||||
router.post('/rights/:id', authenticate, adminController.addUserRight);
|
||||
router.delete('/rights/:id', authenticate, adminController.removeUserRight);
|
||||
|
||||
router.get('/interests/open', authenticate, adminController.getOpenInterests);
|
||||
router.post('/interest', authenticate, adminController.changeInterest);
|
||||
router.post('/interest/translation', authenticate, adminController.changeTranslation);
|
||||
|
||||
@@ -409,6 +409,103 @@ class AdminService {
|
||||
await character.save();
|
||||
}
|
||||
|
||||
// --- User Administration ---
|
||||
async searchUsers(requestingHashedUserId, query) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
if (!query || query.trim().length === 0) return [];
|
||||
|
||||
const users = await User.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ username: { [Op.iLike]: `%${query}%` } },
|
||||
// email is encrypted, can't search directly reliably; skip email search
|
||||
]
|
||||
},
|
||||
attributes: ['id', 'hashedId', 'username', 'active', 'registrationDate']
|
||||
});
|
||||
return users.map(u => ({ id: u.hashedId, username: u.username, active: u.active, registrationDate: u.registrationDate }));
|
||||
}
|
||||
|
||||
async getUserByHashedId(requestingHashedUserId, targetHashedId) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const user = await User.findOne({
|
||||
where: { hashedId: targetHashedId },
|
||||
attributes: ['id', 'hashedId', 'username', 'active', 'registrationDate']
|
||||
});
|
||||
if (!user) throw new Error('notfound');
|
||||
return { id: user.hashedId, username: user.username, active: user.active, registrationDate: user.registrationDate };
|
||||
}
|
||||
|
||||
async updateUser(requestingHashedUserId, targetHashedId, data) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const user = await User.findOne({ where: { hashedId: targetHashedId } });
|
||||
if (!user) throw new Error('notfound');
|
||||
|
||||
const updates = {};
|
||||
if (typeof data.username === 'string' && data.username.trim().length > 0) {
|
||||
updates.username = data.username.trim();
|
||||
}
|
||||
if (typeof data.active === 'boolean') {
|
||||
updates.active = data.active;
|
||||
}
|
||||
if (Object.keys(updates).length === 0) return { id: user.hashedId, username: user.username, active: user.active };
|
||||
await user.update(updates);
|
||||
return { id: user.hashedId, username: user.username, active: user.active };
|
||||
}
|
||||
|
||||
// --- User Rights Administration ---
|
||||
async listUserRightTypes(requestingHashedUserId) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'rights'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const types = await UserRightType.findAll({ attributes: ['id', 'title'] });
|
||||
// map to tr keys if needed; keep title as key used elsewhere
|
||||
return types.map(t => ({ id: t.id, title: t.title }));
|
||||
}
|
||||
|
||||
async listUserRightsForUser(requestingHashedUserId, targetHashedId) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'rights'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const user = await User.findOne({ where: { hashedId: targetHashedId }, attributes: ['id', 'hashedId', 'username'] });
|
||||
if (!user) throw new Error('notfound');
|
||||
const rights = await UserRight.findAll({
|
||||
where: { userId: user.id },
|
||||
include: [{ model: UserRightType, as: 'rightType' }]
|
||||
});
|
||||
return rights.map(r => ({ rightTypeId: r.rightTypeId, title: r.rightType?.title }));
|
||||
}
|
||||
|
||||
async addUserRight(requestingHashedUserId, targetHashedId, rightTypeId) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'rights'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const user = await User.findOne({ where: { hashedId: targetHashedId } });
|
||||
if (!user) throw new Error('notfound');
|
||||
const type = await UserRightType.findByPk(rightTypeId);
|
||||
if (!type) throw new Error('wrongtype');
|
||||
const existing = await UserRight.findOne({ where: { userId: user.id, rightTypeId } });
|
||||
if (existing) return existing; // idempotent
|
||||
const created = await UserRight.create({ userId: user.id, rightTypeId });
|
||||
return created;
|
||||
}
|
||||
|
||||
async removeUserRight(requestingHashedUserId, targetHashedId, rightTypeId) {
|
||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'rights'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const user = await User.findOne({ where: { hashedId: targetHashedId } });
|
||||
if (!user) throw new Error('notfound');
|
||||
await UserRight.destroy({ where: { userId: user.id, rightTypeId } });
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Chat Room Admin ---
|
||||
async getRoomTypes(userId) {
|
||||
if (!(await this.hasUserAccess(userId, 'chatrooms'))) {
|
||||
|
||||
41
frontend/src/components/admin/AdminUserSearch.vue
Normal file
41
frontend/src/components/admin/AdminUserSearch.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="admin-user-search">
|
||||
<div class="search">
|
||||
<input v-model="q" type="text" :placeholder="$t('admin.user.name')" @keyup.enter="search" />
|
||||
<button @click="search">{{ $t('admin.user.search') }}</button>
|
||||
</div>
|
||||
<div v-if="results.length" class="results">
|
||||
<ul>
|
||||
<li v-for="u in results" :key="u.id" @click="$emit('select', u)">{{ u.username }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
|
||||
export default {
|
||||
name: 'AdminUserSearch',
|
||||
emits: ['select'],
|
||||
data() {
|
||||
return { q: '', results: [] };
|
||||
},
|
||||
methods: {
|
||||
async search() {
|
||||
const res = await apiClient.get('/api/admin/users/search', { params: { q: this.q } });
|
||||
this.results = res.data || [];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-user-search { display: grid; gap: 8px; }
|
||||
.search { display: flex; gap: 8px; }
|
||||
ul { list-style: none; padding: 0; margin: 0; }
|
||||
li { padding: 6px 8px; border: 1px solid #ddd; margin-bottom: 6px; cursor: pointer; }
|
||||
li:hover { background: #f5f5f5; }
|
||||
</style>
|
||||
|
||||
|
||||
@@ -23,6 +23,18 @@
|
||||
"editcontactrequest": {
|
||||
"title": "[Admin] - Kontaktanfrage bearbeiten"
|
||||
},
|
||||
"user": {
|
||||
"name": "Benutzername",
|
||||
"active": "Aktiv",
|
||||
"blocked": "Gesperrt",
|
||||
"actions": "Aktionen",
|
||||
"search": "Suchen"
|
||||
},
|
||||
"rights": {
|
||||
"add": "Recht hinzufügen",
|
||||
"select": "Bitte wählen",
|
||||
"current": "Aktuelle Rechte"
|
||||
},
|
||||
"forum": {
|
||||
"title": "[Admin] - Forum",
|
||||
"currentForums": "Existierende Foren",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"admin": {
|
||||
"user": {
|
||||
"name": "Username",
|
||||
"active": "Active",
|
||||
"blocked": "Blocked",
|
||||
"actions": "Actions",
|
||||
"search": "Search"
|
||||
},
|
||||
"rights": {
|
||||
"add": "Add right",
|
||||
"select": "Please select",
|
||||
"current": "Current rights"
|
||||
},
|
||||
"match3": {
|
||||
"title": "Manage Match3 Levels",
|
||||
"newLevel": "Create New Level",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import AdminInterestsView from '../views/admin/InterestsView.vue';
|
||||
import AdminContactsView from '../views/admin/ContactsView.vue';
|
||||
import RoomsView from '../views/admin/RoomsView.vue';
|
||||
import UserRightsView from '../views/admin/UserRightsView.vue';
|
||||
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
||||
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue';
|
||||
import AdminMinigamesView from '../views/admin/MinigamesView.vue';
|
||||
import AdminUsersView from '../views/admin/UsersView.vue';
|
||||
|
||||
const adminRoutes = [
|
||||
{
|
||||
@@ -12,12 +14,24 @@ const adminRoutes = [
|
||||
component: AdminInterestsView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/users',
|
||||
name: 'AdminUsers',
|
||||
component: AdminUsersView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/contacts',
|
||||
name: 'AdminContacts',
|
||||
component: AdminContactsView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/rights',
|
||||
name: 'AdminUserRights',
|
||||
component: UserRightsView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/forum',
|
||||
name: 'AdminForums',
|
||||
|
||||
87
frontend/src/views/admin/UserRightsView.vue
Normal file
87
frontend/src/views/admin/UserRightsView.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="user-rights-view">
|
||||
<h1>{{ $t('navigation.m-administration.userrights') }}</h1>
|
||||
|
||||
<AdminUserSearch @select="selectUser" />
|
||||
|
||||
<div v-if="selected" class="editor">
|
||||
<h2>{{ selected.username }}</h2>
|
||||
<div class="assign">
|
||||
<label>{{ $t('admin.rights.add') }}</label>
|
||||
<select v-model.number="newRightId">
|
||||
<option :value="0">{{ $t('admin.rights.select') }}</option>
|
||||
<option v-for="t in rightTypes" :key="t.id" :value="t.id">{{ t.title }}</option>
|
||||
</select>
|
||||
<button :disabled="!newRightId" @click="addRight">{{ $t('common.add') }}</button>
|
||||
</div>
|
||||
|
||||
<h3>{{ $t('admin.rights.current') }}</h3>
|
||||
<ul class="rights">
|
||||
<li v-for="r in rights" :key="r.rightTypeId">
|
||||
{{ r.title }}
|
||||
<button class="remove" @click="removeRight(r.rightTypeId)">{{ $t('common.delete') }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import AdminUserSearch from '@/components/admin/AdminUserSearch.vue';
|
||||
|
||||
export default {
|
||||
name: 'UserRightsView',
|
||||
components: { AdminUserSearch },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
selected: null,
|
||||
rightTypes: [],
|
||||
rights: [],
|
||||
newRightId: 0
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadRightTypes();
|
||||
}
|
||||
,
|
||||
methods: {
|
||||
async loadRightTypes() {
|
||||
try {
|
||||
const res = await apiClient.get('/api/admin/rights/types');
|
||||
this.rightTypes = res.data || [];
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Laden der Rechte-Typen:', e);
|
||||
}
|
||||
},
|
||||
async selectUser(u) {
|
||||
this.selected = u;
|
||||
const res = await apiClient.get(`/api/admin/rights/${u.id}`);
|
||||
this.rights = res.data || [];
|
||||
this.newRightId = 0;
|
||||
},
|
||||
async addRight() {
|
||||
if (!this.selected || !this.newRightId) return;
|
||||
await apiClient.post(`/api/admin/rights/${this.selected.id}`, { rightTypeId: this.newRightId });
|
||||
await this.selectUser(this.selected);
|
||||
},
|
||||
async removeRight(rightTypeId) {
|
||||
if (!this.selected) return;
|
||||
await apiClient.delete(`/api/admin/rights/${this.selected.id}`, { data: { rightTypeId } });
|
||||
await this.selectUser(this.selected);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-rights-view { padding: 20px; }
|
||||
.content { margin-top: 10px; }
|
||||
.loading, .empty { color: #666; }
|
||||
.rights-table { width: 100%; border-collapse: collapse; }
|
||||
.rights-table th, .rights-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
.rights-table th { background: #f5f5f5; }
|
||||
</style>
|
||||
|
||||
|
||||
68
frontend/src/views/admin/UsersView.vue
Normal file
68
frontend/src/views/admin/UsersView.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="admin-users">
|
||||
<h1>{{ $t('navigation.m-administration.useradministration') }}</h1>
|
||||
|
||||
<AdminUserSearch @select="select" />
|
||||
|
||||
<div v-if="selected" class="edit">
|
||||
<h2>{{ selected.username }}</h2>
|
||||
<label>
|
||||
{{ $t('admin.user.name') }}
|
||||
<input v-model="form.username" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
{{ $t('admin.user.blocked') }}
|
||||
<input type="checkbox" :checked="!form.active" @change="toggleBlocked($event)" />
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button @click="save">{{ $t('common.save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import AdminUserSearch from '@/components/admin/AdminUserSearch.vue';
|
||||
|
||||
export default {
|
||||
name: 'AdminUsersView',
|
||||
components: { AdminUserSearch },
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
form: { username: '', active: true }
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async select(u) {
|
||||
const res = await apiClient.get(`/api/admin/users/${u.id}`);
|
||||
this.selected = res.data;
|
||||
this.form.username = res.data.username;
|
||||
this.form.active = !!res.data.active;
|
||||
},
|
||||
toggleBlocked(e) {
|
||||
this.form.active = !e.target.checked;
|
||||
},
|
||||
async save() {
|
||||
if (!this.selected) return;
|
||||
await apiClient.put(`/api/admin/users/${this.selected.id}`, {
|
||||
username: this.form.username,
|
||||
active: this.form.active
|
||||
});
|
||||
this.$root?.$refs?.messageDialog?.open?.('tr:common.saved');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-users { padding: 20px; }
|
||||
.results table { width: 100%; border-collapse: collapse; }
|
||||
.results th, .results td { border: 1px solid #ddd; padding: 8px; }
|
||||
.edit { margin-top: 16px; display: grid; gap: 10px; max-width: 480px; }
|
||||
.actions { display: flex; gap: 8px; }
|
||||
button { cursor: pointer; }
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user