Implement permission management and enhance user interface for permissions in the application
Add new permission routes and integrate permission checks across various existing routes to ensure proper access control. Update the UserClub model to include role and permissions fields, allowing for more granular user access management. Enhance the frontend by introducing a user dropdown menu for managing permissions and displaying relevant options based on user roles. Improve the overall user experience by implementing permission-based visibility for navigation links and actions throughout the application.
This commit is contained in:
@@ -25,7 +25,7 @@ export const addClub = async (req, res) => {
|
||||
}
|
||||
|
||||
const newClub = await ClubService.createClub(clubName);
|
||||
await ClubService.addUserToClub(user.id, newClub.id);
|
||||
await ClubService.addUserToClub(user.id, newClub.id, true); // true = isOwner
|
||||
res.status(200).json(newClub);
|
||||
} catch (error) {
|
||||
console.error('[addClub] - error:', error);
|
||||
|
||||
152
backend/controllers/permissionController.js
Normal file
152
backend/controllers/permissionController.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import permissionService from '../services/permissionService.js';
|
||||
|
||||
/**
|
||||
* Get user's permissions for a club
|
||||
*/
|
||||
export const getUserPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const permissions = await permissionService.getUserClubPermissions(userId, parseInt(clubId));
|
||||
|
||||
if (!permissions) {
|
||||
return res.status(404).json({ error: 'Keine Berechtigungen gefunden' });
|
||||
}
|
||||
|
||||
res.json(permissions);
|
||||
} catch (error) {
|
||||
console.error('Error getting user permissions:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Berechtigungen' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all club members with their permissions
|
||||
*/
|
||||
export const getClubMembersWithPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const members = await permissionService.getClubMembersWithPermissions(
|
||||
parseInt(clubId),
|
||||
userId
|
||||
);
|
||||
|
||||
res.json(members);
|
||||
} catch (error) {
|
||||
console.error('Error getting club members with permissions:', error);
|
||||
if (error.message === 'Keine Berechtigung zum Anzeigen von Berechtigungen') {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Mitglieder' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user role
|
||||
*/
|
||||
export const updateUserRole = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { role } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setUserRole(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
role,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user custom permissions
|
||||
*/
|
||||
export const updateUserPermissions = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { permissions } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setCustomPermissions(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
permissions,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user permissions:', error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get available roles
|
||||
*/
|
||||
export const getAvailableRoles = async (req, res) => {
|
||||
try {
|
||||
const roles = permissionService.getAvailableRoles();
|
||||
res.json(roles);
|
||||
} catch (error) {
|
||||
console.error('Error getting available roles:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Rollen' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get permission structure
|
||||
*/
|
||||
export const getPermissionStructure = async (req, res) => {
|
||||
try {
|
||||
const structure = permissionService.getPermissionStructure();
|
||||
res.json(structure);
|
||||
} catch (error) {
|
||||
console.error('Error getting permission structure:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Berechtigungsstruktur' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user status (activate/deactivate)
|
||||
*/
|
||||
export const updateUserStatus = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId: targetUserId } = req.params;
|
||||
const { approved } = req.body;
|
||||
const updatingUserId = req.user.id;
|
||||
|
||||
const result = await permissionService.setUserStatus(
|
||||
parseInt(targetUserId),
|
||||
parseInt(clubId),
|
||||
approved,
|
||||
updatingUserId
|
||||
);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error updating user status:', error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getUserPermissions,
|
||||
getClubMembersWithPermissions,
|
||||
updateUserRole,
|
||||
updateUserPermissions,
|
||||
updateUserStatus,
|
||||
getAvailableRoles,
|
||||
getPermissionStructure
|
||||
};
|
||||
|
||||
|
||||
187
backend/middleware/authorizationMiddleware.js
Normal file
187
backend/middleware/authorizationMiddleware.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import permissionService from '../services/permissionService.js';
|
||||
|
||||
/**
|
||||
* Authorization Middleware
|
||||
* Checks if user has permission to access a resource
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if user has permission for a specific resource and action
|
||||
* @param {string} resource - Resource name (diary, members, teams, etc.)
|
||||
* @param {string} action - Action type (read, write, delete)
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const authorize = (resource, action = 'read') => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
// Get clubId from various possible sources
|
||||
const clubId = req.params.clubId || req.params.id || req.body.clubId || req.query.clubId;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
// Check permission
|
||||
const hasPermission = await permissionService.hasPermission(
|
||||
userId,
|
||||
parseInt(clubId),
|
||||
resource,
|
||||
action
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: `Fehlende Berechtigung: ${resource}.${action}`
|
||||
});
|
||||
}
|
||||
|
||||
// Store permissions in request for later use
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
req.userPermissions = userPermissions;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Authorization error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user is club owner
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireOwner = () => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId = req.params.clubId || req.params.id || req.body.clubId || req.query.clubId;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || !userPermissions.isOwner) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: 'Nur der Club-Ersteller hat Zugriff'
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Owner check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user is admin (owner or admin role)
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireAdmin = () => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId = req.params.clubId || req.params.id || req.body.clubId || req.query.clubId;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || (userPermissions.role !== 'admin' && !userPermissions.isOwner)) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: 'Administrator-Rechte erforderlich'
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Admin check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user has any of the specified roles
|
||||
* @param {string[]} roles - Array of allowed roles
|
||||
* @returns {Function} Express middleware function
|
||||
*/
|
||||
export const requireRole = (roles) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||
}
|
||||
|
||||
const clubId = req.params.clubId || req.params.id || req.body.clubId || req.query.clubId;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ error: 'Club-ID fehlt' });
|
||||
}
|
||||
|
||||
const userPermissions = await permissionService.getUserClubPermissions(
|
||||
userId,
|
||||
parseInt(clubId)
|
||||
);
|
||||
|
||||
if (!userPermissions || !roles.includes(userPermissions.role)) {
|
||||
return res.status(403).json({
|
||||
error: 'Keine Berechtigung',
|
||||
details: `Erforderliche Rolle: ${roles.join(', ')}`
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Role check error:', error);
|
||||
res.status(500).json({ error: 'Fehler bei der Berechtigungsprüfung' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
authorize,
|
||||
requireOwner,
|
||||
requireAdmin,
|
||||
requireRole
|
||||
};
|
||||
|
||||
17
backend/migrations/add_permissions_to_user_club.sql
Normal file
17
backend/migrations/add_permissions_to_user_club.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Add role and permissions columns to user_club table
|
||||
ALTER TABLE `user_club`
|
||||
ADD COLUMN `role` VARCHAR(50) DEFAULT 'member' COMMENT 'User role: admin, trainer, team_manager, member' AFTER `approved`,
|
||||
ADD COLUMN `permissions` JSON NULL COMMENT 'Specific permissions: {diary: {read: true, write: true}, members: {...}, ...}' AFTER `role`,
|
||||
ADD COLUMN `is_owner` BOOLEAN DEFAULT FALSE COMMENT 'True if user created the club' AFTER `permissions`;
|
||||
|
||||
-- Create index for faster role lookups
|
||||
CREATE INDEX `idx_user_club_role` ON `user_club` (`role`);
|
||||
CREATE INDEX `idx_user_club_owner` ON `user_club` (`is_owner`);
|
||||
|
||||
-- Set existing approved users as members
|
||||
UPDATE `user_club` SET `role` = 'member' WHERE `approved` = 1 AND `role` IS NULL;
|
||||
|
||||
-- If there's a user who created the club (we need to identify them somehow)
|
||||
-- For now, we'll need to manually set the owner after migration
|
||||
|
||||
|
||||
38
backend/migrations/update_existing_user_club_permissions.sql
Normal file
38
backend/migrations/update_existing_user_club_permissions.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- Update existing user_club entries with default permissions
|
||||
-- This migration sets default values for role and is_owner for existing club memberships
|
||||
|
||||
-- Set default role to 'member' for all approved users who don't have a role yet
|
||||
UPDATE `user_club`
|
||||
SET `role` = 'member'
|
||||
WHERE `approved` = 1
|
||||
AND (`role` IS NULL OR `role` = '');
|
||||
|
||||
-- Optionally: Set the first approved user of each club as owner
|
||||
-- This finds the user with the lowest user_id per club (oldest member) and marks them as owner
|
||||
UPDATE `user_club` AS uc1
|
||||
INNER JOIN (
|
||||
SELECT `club_id`, MIN(`user_id`) as `first_user_id`
|
||||
FROM `user_club`
|
||||
WHERE `approved` = 1
|
||||
GROUP BY `club_id`
|
||||
) AS uc2 ON uc1.`club_id` = uc2.`club_id` AND uc1.`user_id` = uc2.`first_user_id`
|
||||
SET
|
||||
uc1.`is_owner` = 1,
|
||||
uc1.`role` = 'admin';
|
||||
|
||||
-- Verify the changes
|
||||
SELECT
|
||||
uc.`club_id`,
|
||||
c.`name` as club_name,
|
||||
uc.`user_id`,
|
||||
u.`email` as user_email,
|
||||
uc.`role`,
|
||||
uc.`is_owner`,
|
||||
uc.`approved`
|
||||
FROM `user_club` uc
|
||||
LEFT JOIN `club` c ON c.`id` = uc.`club_id`
|
||||
LEFT JOIN `user` u ON u.`id` = uc.`user_id`
|
||||
WHERE uc.`approved` = 1
|
||||
ORDER BY uc.`club_id`, uc.`is_owner` DESC, uc.`user_id`;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import Club from './Club.js';
|
||||
const UserClub = sequelize.define('UserClub', {
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id',
|
||||
@@ -13,6 +14,7 @@ const UserClub = sequelize.define('UserClub', {
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
@@ -22,6 +24,23 @@ const UserClub = sequelize.define('UserClub', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
},
|
||||
role: {
|
||||
type: DataTypes.STRING(50),
|
||||
defaultValue: 'member',
|
||||
allowNull: false,
|
||||
comment: 'User role: admin, trainer, team_manager, member'
|
||||
},
|
||||
permissions: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: 'Specific permissions: {diary: {read: true, write: true}, members: {...}, ...}'
|
||||
},
|
||||
isOwner: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
comment: 'True if user created the club'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'user_club',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import { getClubs, addClub, getClub, requestClubAccess, getPendingApprovals, approveClubAccess, rejectClubAccess } from '../controllers/clubsController.js';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -8,8 +9,8 @@ router.get('/', authenticate, getClubs);
|
||||
router.post('/', authenticate, addClub);
|
||||
router.get('/:clubid', authenticate, getClub);
|
||||
router.get('/request/:clubid', authenticate, requestClubAccess);
|
||||
router.get('/pending/:clubid', authenticate, getPendingApprovals);
|
||||
router.post('/approve', authenticate, approveClubAccess);
|
||||
router.post('/reject', authenticate, rejectClubAccess);
|
||||
router.get('/pending/:clubid', authenticate, authorize('approvals', 'read'), getPendingApprovals);
|
||||
router.post('/approve', authenticate, authorize('approvals', 'write'), approveClubAccess);
|
||||
router.post('/reject', authenticate, authorize('approvals', 'write'), rejectClubAccess);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import {
|
||||
getDatesForClub,
|
||||
createDateForClub,
|
||||
@@ -14,14 +15,14 @@ import {
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/note', authenticate, addDiaryNote);
|
||||
router.delete('/note/:noteId', authenticate, deleteDiaryNote);
|
||||
router.post('/tag', authenticate, addDiaryTag);
|
||||
router.post('/tag/:clubId/add-tag', authenticate, addTagToDiaryDate);
|
||||
router.delete('/:clubId/tag', authenticate, deleteTagFromDiaryDate);
|
||||
router.get('/:clubId', authenticate, getDatesForClub);
|
||||
router.post('/:clubId', authenticate, createDateForClub);
|
||||
router.put('/:clubId', authenticate, updateTrainingTimes);
|
||||
router.delete('/:clubId/:dateId', authenticate, deleteDateForClub);
|
||||
router.post('/note', authenticate, authorize('diary', 'write'), addDiaryNote);
|
||||
router.delete('/note/:noteId', authenticate, authorize('diary', 'delete'), deleteDiaryNote);
|
||||
router.post('/tag', authenticate, authorize('diary', 'write'), addDiaryTag);
|
||||
router.post('/tag/:clubId/add-tag', authenticate, authorize('diary', 'write'), addTagToDiaryDate);
|
||||
router.delete('/:clubId/tag', authenticate, authorize('diary', 'delete'), deleteTagFromDiaryDate);
|
||||
router.get('/:clubId', authenticate, authorize('diary', 'read'), getDatesForClub);
|
||||
router.post('/:clubId', authenticate, authorize('diary', 'write'), createDateForClub);
|
||||
router.put('/:clubId', authenticate, authorize('diary', 'write'), updateTrainingTimes);
|
||||
router.delete('/:clubId/:dateId', authenticate, authorize('diary', 'delete'), deleteDateForClub);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import express from 'express';
|
||||
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats } from '../controllers/matchController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import multer from 'multer';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const upload = multer({ dest: 'uploads/' });
|
||||
|
||||
router.post('/import', authenticate, upload.single('file'), uploadCSV);
|
||||
router.get('/leagues/current/:clubId', authenticate, getLeaguesForCurrentSeason);
|
||||
router.get('/leagues/:clubId/matches/:leagueId', authenticate, getMatchesForLeague);
|
||||
router.get('/leagues/:clubId/matches', authenticate, getMatchesForLeagues);
|
||||
router.get('/leagues/:clubId/table/:leagueId', authenticate, getLeagueTable);
|
||||
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, fetchLeagueTableFromMyTischtennis);
|
||||
router.patch('/:matchId/players', authenticate, updateMatchPlayers);
|
||||
router.get('/leagues/:clubId/stats/:leagueId', authenticate, getPlayerMatchStats);
|
||||
router.post('/import', authenticate, authorize('schedule', 'write'), upload.single('file'), uploadCSV);
|
||||
router.get('/leagues/current/:clubId', authenticate, authorize('schedule', 'read'), getLeaguesForCurrentSeason);
|
||||
router.get('/leagues/:clubId/matches/:leagueId', authenticate, authorize('schedule', 'read'), getMatchesForLeague);
|
||||
router.get('/leagues/:clubId/matches', authenticate, authorize('schedule', 'read'), getMatchesForLeagues);
|
||||
router.get('/leagues/:clubId/table/:leagueId', authenticate, authorize('schedule', 'read'), getLeagueTable);
|
||||
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, authorize('mytischtennis', 'write'), fetchLeagueTableFromMyTischtennis);
|
||||
router.patch('/:matchId/players', authenticate, authorize('schedule', 'write'), updateMatchPlayers);
|
||||
router.get('/leagues/:clubId/stats/:leagueId', authenticate, authorize('schedule', 'read'), getPlayerMatchStats);
|
||||
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage } from '../controllers/memberController.js';
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import multer from 'multer';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -8,12 +9,12 @@ const router = express.Router();
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage: storage });
|
||||
|
||||
router.post('/image/:clubId/:memberId', authenticate, upload.single('image'), uploadMemberImage);
|
||||
router.get('/image/:clubId/:memberId', authenticate, getMemberImage);
|
||||
router.get('/get/:id/:showAll', authenticate, getClubMembers);
|
||||
router.post('/set/:id', authenticate, setClubMembers);
|
||||
router.get('/notapproved/:id', authenticate, getWaitingApprovals);
|
||||
router.post('/update-ratings/:id', authenticate, updateRatingsFromMyTischtennis);
|
||||
router.post('/rotate-image/:clubId/:memberId', authenticate, rotateMemberImage);
|
||||
router.post('/image/:clubId/:memberId', authenticate, authorize('members', 'write'), upload.single('image'), uploadMemberImage);
|
||||
router.get('/image/:clubId/:memberId', authenticate, authorize('members', 'read'), getMemberImage);
|
||||
router.get('/get/:id/:showAll', authenticate, authorize('members', 'read'), getClubMembers);
|
||||
router.post('/set/:id', authenticate, authorize('members', 'write'), setClubMembers);
|
||||
router.get('/notapproved/:id', authenticate, authorize('members', 'read'), getWaitingApprovals);
|
||||
router.post('/update-ratings/:id', authenticate, authorize('mytischtennis', 'write'), updateRatingsFromMyTischtennis);
|
||||
router.post('/rotate-image/:clubId/:memberId', authenticate, authorize('members', 'write'), rotateMemberImage);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -2,23 +2,24 @@ import express from 'express';
|
||||
import myTischtennisController from '../controllers/myTischtennisController.js';
|
||||
import myTischtennisUrlController from '../controllers/myTischtennisUrlController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticate);
|
||||
|
||||
// GET /api/mytischtennis/account - Get account
|
||||
// GET /api/mytischtennis/account - Get account (alle dürfen lesen)
|
||||
router.get('/account', myTischtennisController.getAccount);
|
||||
|
||||
// GET /api/mytischtennis/status - Check status
|
||||
// GET /api/mytischtennis/status - Check status (alle dürfen lesen)
|
||||
router.get('/status', myTischtennisController.getStatus);
|
||||
|
||||
// POST /api/mytischtennis/account - Create or update account
|
||||
// POST /api/mytischtennis/account - Create or update account (alle dürfen bearbeiten)
|
||||
router.post('/account', myTischtennisController.upsertAccount);
|
||||
|
||||
// DELETE /api/mytischtennis/account - Delete account
|
||||
router.delete('/account', myTischtennisController.deleteAccount);
|
||||
// DELETE /api/mytischtennis/account - Delete account (nur Admin)
|
||||
router.delete('/account', authorize('mytischtennis_admin', 'write'), myTischtennisController.deleteAccount);
|
||||
|
||||
// POST /api/mytischtennis/verify - Verify login
|
||||
router.post('/verify', myTischtennisController.verifyLogin);
|
||||
|
||||
30
backend/routes/permissionRoutes.js
Normal file
30
backend/routes/permissionRoutes.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize, requireAdmin } from '../middleware/authorizationMiddleware.js';
|
||||
import permissionController from '../controllers/permissionController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get available roles (no club context needed)
|
||||
router.get('/roles/available', authenticate, permissionController.getAvailableRoles);
|
||||
|
||||
// Get permission structure (no club context needed)
|
||||
router.get('/structure/all', authenticate, permissionController.getPermissionStructure);
|
||||
|
||||
// Get current user's permissions for a club (no authorization check - needed to load permissions)
|
||||
router.get('/:clubId', authenticate, permissionController.getUserPermissions);
|
||||
|
||||
// Get all club members with their permissions (admin only)
|
||||
router.get('/:clubId/members', authenticate, authorize('permissions', 'read'), permissionController.getClubMembersWithPermissions);
|
||||
|
||||
// Update user role (admin only)
|
||||
router.put('/:clubId/user/:userId/role', authenticate, authorize('permissions', 'write'), permissionController.updateUserRole);
|
||||
|
||||
// Update user permissions (admin only)
|
||||
router.put('/:clubId/user/:userId/permissions', authenticate, authorize('permissions', 'write'), permissionController.updateUserPermissions);
|
||||
|
||||
// Update user status (admin only)
|
||||
router.put('/:clubId/user/:userId/status', authenticate, authorize('permissions', 'write'), permissionController.updateUserStatus);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../controllers/predefinedActivityController.js';
|
||||
import multer from 'multer';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import { uploadPredefinedActivityImage, deletePredefinedActivityImage } from '../controllers/predefinedActivityImageController.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import path from 'path';
|
||||
@@ -18,16 +19,16 @@ import fs from 'fs';
|
||||
const router = express.Router();
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
|
||||
router.post('/', authenticate, createPredefinedActivity);
|
||||
router.get('/', authenticate, getAllPredefinedActivities);
|
||||
router.get('/:id', authenticate, getPredefinedActivityById);
|
||||
router.put('/:id', authenticate, updatePredefinedActivity);
|
||||
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.put('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.delete('/:id/image/:imageId', authenticate, deletePredefinedActivityImage);
|
||||
router.get('/search/query', authenticate, searchPredefinedActivities);
|
||||
router.post('/merge', authenticate, mergePredefinedActivities);
|
||||
router.post('/deduplicate', authenticate, deduplicatePredefinedActivities);
|
||||
router.post('/', authenticate, authorize('predefined_activities', 'write'), createPredefinedActivity);
|
||||
router.get('/', authenticate, authorize('predefined_activities', 'read'), getAllPredefinedActivities);
|
||||
router.get('/:id', authenticate, authorize('predefined_activities', 'read'), getPredefinedActivityById);
|
||||
router.put('/:id', authenticate, authorize('predefined_activities', 'write'), updatePredefinedActivity);
|
||||
router.post('/:id/image', authenticate, authorize('predefined_activities', 'write'), upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.put('/:id/image', authenticate, authorize('predefined_activities', 'write'), upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.delete('/:id/image/:imageId', authenticate, authorize('predefined_activities', 'delete'), deletePredefinedActivityImage);
|
||||
router.get('/search/query', authenticate, authorize('predefined_activities', 'read'), searchPredefinedActivities);
|
||||
router.post('/merge', authenticate, authorize('predefined_activities', 'write'), mergePredefinedActivities);
|
||||
router.post('/deduplicate', authenticate, authorize('predefined_activities', 'write'), deduplicatePredefinedActivities);
|
||||
router.get('/:id/image/:imageId', async (req, res) => {
|
||||
try {
|
||||
const { id, imageId } = req.params;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
import {
|
||||
getTeams,
|
||||
getTeam,
|
||||
@@ -12,21 +13,21 @@ import {
|
||||
const router = express.Router();
|
||||
|
||||
// Get all teams for a club
|
||||
router.get('/club/:clubid', authenticate, getTeams);
|
||||
router.get('/club/:clubid', authenticate, authorize('teams', 'read'), getTeams);
|
||||
|
||||
// Get leagues for a club
|
||||
router.get('/leagues/:clubid', authenticate, getLeagues);
|
||||
router.get('/leagues/:clubid', authenticate, authorize('teams', 'read'), getLeagues);
|
||||
|
||||
// Get a specific team
|
||||
router.get('/:teamid', authenticate, getTeam);
|
||||
router.get('/:teamid', authenticate, authorize('teams', 'read'), getTeam);
|
||||
|
||||
// Create a new team
|
||||
router.post('/club/:clubid', authenticate, createTeam);
|
||||
router.post('/club/:clubid', authenticate, authorize('teams', 'write'), createTeam);
|
||||
|
||||
// Update a team
|
||||
router.put('/:teamid', authenticate, updateTeam);
|
||||
router.put('/:teamid', authenticate, authorize('teams', 'write'), updateTeam);
|
||||
|
||||
// Delete a team
|
||||
router.delete('/:teamid', authenticate, deleteTeam);
|
||||
router.delete('/:teamid', authenticate, authorize('teams', 'delete'), deleteTeam);
|
||||
|
||||
export default router;
|
||||
141
backend/scripts/createTestUsers.js
Normal file
141
backend/scripts/createTestUsers.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import User from '../models/User.js';
|
||||
import Club from '../models/Club.js';
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
/**
|
||||
* Create test users with different roles
|
||||
*/
|
||||
|
||||
const TEST_USERS = [
|
||||
{
|
||||
email: 'admin@test.de',
|
||||
password: 'test123',
|
||||
role: 'admin',
|
||||
isOwner: false
|
||||
},
|
||||
{
|
||||
email: 'trainer@test.de',
|
||||
password: 'test123',
|
||||
role: 'trainer',
|
||||
isOwner: false
|
||||
},
|
||||
{
|
||||
email: 'teammanager@test.de',
|
||||
password: 'test123',
|
||||
role: 'team_manager',
|
||||
isOwner: false
|
||||
},
|
||||
{
|
||||
email: 'tournamentmanager@test.de',
|
||||
password: 'test123',
|
||||
role: 'tournament_manager',
|
||||
isOwner: false
|
||||
},
|
||||
{
|
||||
email: 'member1@test.de',
|
||||
password: 'test123',
|
||||
role: 'member',
|
||||
isOwner: false
|
||||
},
|
||||
{
|
||||
email: 'member2@test.de',
|
||||
password: 'test123',
|
||||
role: 'member',
|
||||
isOwner: false
|
||||
}
|
||||
];
|
||||
|
||||
async function createTestUsers() {
|
||||
console.log('Creating test users...\n');
|
||||
|
||||
try {
|
||||
// Get first club (or specify club ID)
|
||||
const clubs = await Club.findAll({ limit: 1 });
|
||||
|
||||
if (clubs.length === 0) {
|
||||
console.error('❌ No clubs found! Please create a club first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const club = clubs[0];
|
||||
console.log(`Using club: ${club.name} (ID: ${club.id})\n`);
|
||||
|
||||
for (const userData of TEST_USERS) {
|
||||
console.log(`Creating user: ${userData.email} (${userData.role})...`);
|
||||
|
||||
// Check if user already exists
|
||||
let user = await User.findOne({ where: { email: userData.email } });
|
||||
|
||||
if (user) {
|
||||
console.log(` ⚠️ User already exists, using existing user`);
|
||||
} else {
|
||||
// Create user
|
||||
user = await User.create({
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
isActive: true
|
||||
});
|
||||
console.log(` ✓ User created`);
|
||||
}
|
||||
|
||||
// Check if user is already in club
|
||||
let userClub = await UserClub.findOne({
|
||||
where: {
|
||||
userId: user.id,
|
||||
clubId: club.id
|
||||
}
|
||||
});
|
||||
|
||||
if (userClub) {
|
||||
console.log(` ⚠️ User already in club, updating role...`);
|
||||
await userClub.update({
|
||||
role: userData.role,
|
||||
isOwner: userData.isOwner,
|
||||
approved: true
|
||||
});
|
||||
console.log(` ✓ Updated to role: ${userData.role}`);
|
||||
} else {
|
||||
// Add user to club
|
||||
userClub = await UserClub.create({
|
||||
userId: user.id,
|
||||
clubId: club.id,
|
||||
role: userData.role,
|
||||
isOwner: userData.isOwner,
|
||||
approved: true
|
||||
});
|
||||
console.log(` ✓ Added to club with role: ${userData.role}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✅ Test users created successfully!\n');
|
||||
|
||||
// Show summary
|
||||
console.log('Summary:');
|
||||
console.log('========================================');
|
||||
console.log(`Club: ${club.name}`);
|
||||
console.log('\nTest Users:');
|
||||
|
||||
for (const userData of TEST_USERS) {
|
||||
console.log(` ${userData.email.padEnd(25)} | ${userData.role.padEnd(15)} | Password: test123`);
|
||||
}
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('You can now login with any of these users!');
|
||||
console.log('All passwords are: test123');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating test users:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run
|
||||
createTestUsers().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
128
backend/scripts/migratePermissions.js
Normal file
128
backend/scripts/migratePermissions.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import Club from '../models/Club.js';
|
||||
import User from '../models/User.js';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
/**
|
||||
* Migration script to set up permissions for existing clubs
|
||||
* This script:
|
||||
* 1. Sets default role='member' for all approved users without a role
|
||||
* 2. Identifies and marks the first user (by creation date) of each club as owner
|
||||
*/
|
||||
|
||||
async function migratePermissions() {
|
||||
console.log('Starting permissions migration...\n');
|
||||
|
||||
try {
|
||||
// Get all clubs
|
||||
const clubs = await Club.findAll({
|
||||
include: [{
|
||||
model: UserClub,
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user'
|
||||
}],
|
||||
where: {
|
||||
approved: true
|
||||
},
|
||||
order: [['createdAt', 'ASC']]
|
||||
}]
|
||||
});
|
||||
|
||||
console.log(`Found ${clubs.length} club(s)\n`);
|
||||
|
||||
for (const club of clubs) {
|
||||
console.log(`\n--- Club: ${club.name} (ID: ${club.id}) ---`);
|
||||
|
||||
const userClubs = await UserClub.findAll({
|
||||
where: {
|
||||
clubId: club.id,
|
||||
approved: true
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user'
|
||||
}],
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
if (userClubs.length === 0) {
|
||||
console.log(' No approved members found.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// First user becomes owner
|
||||
const firstUser = userClubs[0];
|
||||
|
||||
console.log(` Members found: ${userClubs.length}`);
|
||||
console.log(` First member (will be owner): ${firstUser.user.email}`);
|
||||
|
||||
for (let i = 0; i < userClubs.length; i++) {
|
||||
const userClub = userClubs[i];
|
||||
const isFirstUser = i === 0;
|
||||
|
||||
// Set role if not set
|
||||
if (!userClub.role) {
|
||||
userClub.role = isFirstUser ? 'admin' : 'member';
|
||||
}
|
||||
|
||||
// Set owner flag
|
||||
userClub.isOwner = isFirstUser;
|
||||
|
||||
await userClub.save();
|
||||
|
||||
console.log(` ✓ Updated ${userClub.user.email}: role=${userClub.role}, isOwner=${userClub.isOwner}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✅ Migration completed successfully!');
|
||||
console.log('\nSummary:');
|
||||
|
||||
// Show summary
|
||||
const owners = await UserClub.findAll({
|
||||
where: {
|
||||
isOwner: true
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'user'
|
||||
},
|
||||
{
|
||||
model: Club,
|
||||
as: 'club'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
console.log(`\nClub Owners (${owners.length}):`);
|
||||
for (const owner of owners) {
|
||||
console.log(` - ${owner.club.name}: ${owner.user.email}`);
|
||||
}
|
||||
|
||||
const admins = await UserClub.count({
|
||||
where: { role: 'admin' }
|
||||
});
|
||||
const members = await UserClub.count({
|
||||
where: { role: 'member' }
|
||||
});
|
||||
|
||||
console.log(`\nRole Distribution:`);
|
||||
console.log(` - Admins: ${admins}`);
|
||||
console.log(` - Members: ${members}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Migration failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration
|
||||
migratePermissions().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
103
backend/scripts/quickFixOwner.js
Normal file
103
backend/scripts/quickFixOwner.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import Club from '../models/Club.js';
|
||||
import User from '../models/User.js';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
/**
|
||||
* Quick fix: Set first user of each club as owner/admin
|
||||
* This is a simplified version for immediate use
|
||||
*/
|
||||
|
||||
async function quickFixOwners() {
|
||||
console.log('Quick Fix: Setting club owners...\n');
|
||||
|
||||
try {
|
||||
const clubs = await Club.findAll();
|
||||
|
||||
console.log(`Found ${clubs.length} club(s)\n`);
|
||||
|
||||
for (const club of clubs) {
|
||||
console.log(`Club: ${club.name} (ID: ${club.id})`);
|
||||
|
||||
// Find all approved members, ordered by creation date
|
||||
const userClubs = await UserClub.findAll({
|
||||
where: {
|
||||
clubId: club.id,
|
||||
approved: true
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'email']
|
||||
}],
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
if (userClubs.length === 0) {
|
||||
console.log(' ⚠️ No approved members\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
// First user becomes owner
|
||||
const firstUserClub = userClubs[0];
|
||||
|
||||
// Reset all users first (remove owner flag)
|
||||
await UserClub.update(
|
||||
{ isOwner: false },
|
||||
{
|
||||
where: {
|
||||
clubId: club.id,
|
||||
approved: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Set first user as owner and admin
|
||||
await firstUserClub.update({
|
||||
isOwner: true,
|
||||
role: 'admin'
|
||||
});
|
||||
|
||||
console.log(` ✅ Owner: ${firstUserClub.user.email}`);
|
||||
|
||||
// Set role for other members if not set
|
||||
for (let i = 1; i < userClubs.length; i++) {
|
||||
const uc = userClubs[i];
|
||||
if (!uc.role) {
|
||||
await uc.update({ role: 'member' });
|
||||
console.log(` 👤 Member: ${uc.user.email}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log('✅ Quick fix completed!\n');
|
||||
|
||||
// Show all owners
|
||||
const owners = await UserClub.findAll({
|
||||
where: { isOwner: true },
|
||||
include: [
|
||||
{ model: User, as: 'user', attributes: ['email'] },
|
||||
{ model: Club, as: 'club', attributes: ['name'] }
|
||||
]
|
||||
});
|
||||
|
||||
console.log('Current Club Owners:');
|
||||
for (const owner of owners) {
|
||||
console.log(` 📍 ${owner.club.name}: ${owner.user.email} (role: ${owner.role})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
quickFixOwners().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ import clubTeamRoutes from './routes/clubTeamRoutes.js';
|
||||
import teamDocumentRoutes from './routes/teamDocumentRoutes.js';
|
||||
import seasonRoutes from './routes/seasonRoutes.js';
|
||||
import memberActivityRoutes from './routes/memberActivityRoutes.js';
|
||||
import permissionRoutes from './routes/permissionRoutes.js';
|
||||
import schedulerService from './services/schedulerService.js';
|
||||
|
||||
const app = express();
|
||||
@@ -90,6 +91,7 @@ app.use('/api/club-teams', clubTeamRoutes);
|
||||
app.use('/api/team-documents', teamDocumentRoutes);
|
||||
app.use('/api/seasons', seasonRoutes);
|
||||
app.use('/api/member-activities', memberActivityRoutes);
|
||||
app.use('/api/permissions', permissionRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import User from '../models/User.js';
|
||||
import Member from '../models/Member.js';
|
||||
import { Op, fn, where, col } from 'sequelize';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import permissionService from './permissionService.js';
|
||||
|
||||
class ClubService {
|
||||
async getAllClubs() {
|
||||
@@ -20,8 +21,15 @@ class ClubService {
|
||||
return await Club.create({ name: clubName });
|
||||
}
|
||||
|
||||
async addUserToClub(userId, clubId) {
|
||||
return await UserClub.create({ userId: userId, clubId: clubId, approved: true });
|
||||
async addUserToClub(userId, clubId, isOwner = false) {
|
||||
const userClub = await UserClub.create({
|
||||
userId: userId,
|
||||
clubId: clubId,
|
||||
approved: true,
|
||||
isOwner: isOwner,
|
||||
role: isOwner ? 'admin' : 'member'
|
||||
});
|
||||
return userClub;
|
||||
}
|
||||
|
||||
async getUserClubAccess(userId, clubId) {
|
||||
|
||||
366
backend/services/permissionService.js
Normal file
366
backend/services/permissionService.js
Normal file
@@ -0,0 +1,366 @@
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import Club from '../models/Club.js';
|
||||
import User from '../models/User.js';
|
||||
|
||||
/**
|
||||
* Permission Service
|
||||
* Handles all permission-related logic
|
||||
*/
|
||||
|
||||
// Default permissions for each role
|
||||
const ROLE_PERMISSIONS = {
|
||||
admin: {
|
||||
diary: { read: true, write: true, delete: true },
|
||||
members: { read: true, write: true, delete: true },
|
||||
teams: { read: true, write: true, delete: true },
|
||||
schedule: { read: true, write: true, delete: true },
|
||||
tournaments: { read: true, write: true, delete: true },
|
||||
statistics: { read: true, write: true },
|
||||
settings: { read: true, write: true },
|
||||
permissions: { read: true, write: true }, // Can manage other users' permissions
|
||||
approvals: { read: true, write: true },
|
||||
mytischtennis_admin: { read: true, write: true },
|
||||
predefined_activities: { read: true, write: true, delete: true }
|
||||
},
|
||||
trainer: {
|
||||
diary: { read: true, write: true, delete: true },
|
||||
members: { read: true, write: true, delete: false },
|
||||
teams: { read: true, write: true, delete: false },
|
||||
schedule: { read: true, write: true, delete: false },
|
||||
tournaments: { read: true, write: true, delete: false },
|
||||
statistics: { read: true, write: false },
|
||||
settings: { read: false, write: false },
|
||||
permissions: { read: false, write: false },
|
||||
approvals: { read: false, write: false },
|
||||
mytischtennis_admin: { read: false, write: false },
|
||||
predefined_activities: { read: true, write: true, delete: true }
|
||||
},
|
||||
team_manager: {
|
||||
diary: { read: false, write: false, delete: false },
|
||||
members: { read: true, write: false, delete: false },
|
||||
teams: { read: true, write: true, delete: false },
|
||||
schedule: { read: true, write: true, delete: false },
|
||||
tournaments: { read: true, write: false, delete: false },
|
||||
statistics: { read: true, write: false },
|
||||
settings: { read: false, write: false },
|
||||
permissions: { read: false, write: false },
|
||||
approvals: { read: false, write: false },
|
||||
mytischtennis_admin: { read: false, write: false },
|
||||
predefined_activities: { read: false, write: false, delete: false }
|
||||
},
|
||||
tournament_manager: {
|
||||
diary: { read: false, write: false, delete: false },
|
||||
members: { read: true, write: false, delete: false },
|
||||
teams: { read: false, write: false, delete: false },
|
||||
schedule: { read: false, write: false, delete: false },
|
||||
tournaments: { read: true, write: true, delete: false },
|
||||
statistics: { read: true, write: false },
|
||||
settings: { read: false, write: false },
|
||||
permissions: { read: false, write: false },
|
||||
approvals: { read: false, write: false },
|
||||
mytischtennis_admin: { read: false, write: false },
|
||||
predefined_activities: { read: false, write: false, delete: false }
|
||||
},
|
||||
member: {
|
||||
diary: { read: false, write: false, delete: false },
|
||||
members: { read: false, write: false, delete: false },
|
||||
teams: { read: false, write: false, delete: false },
|
||||
schedule: { read: false, write: false, delete: false },
|
||||
tournaments: { read: false, write: false, delete: false },
|
||||
statistics: { read: true, write: false },
|
||||
settings: { read: false, write: false },
|
||||
permissions: { read: false, write: false },
|
||||
approvals: { read: false, write: false },
|
||||
mytischtennis_admin: { read: false, write: false },
|
||||
predefined_activities: { read: false, write: false, delete: false }
|
||||
}
|
||||
};
|
||||
|
||||
class PermissionService {
|
||||
/**
|
||||
* Get user's permissions for a specific club
|
||||
*/
|
||||
async getUserClubPermissions(userId, clubId) {
|
||||
const userClub = await UserClub.findOne({
|
||||
where: {
|
||||
userId,
|
||||
clubId,
|
||||
approved: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!userClub) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If user is owner, they have full admin rights
|
||||
if (userClub.isOwner) {
|
||||
return {
|
||||
role: 'admin',
|
||||
isOwner: true,
|
||||
permissions: ROLE_PERMISSIONS.admin
|
||||
};
|
||||
}
|
||||
|
||||
// Get role from database, fallback to 'member' if null/undefined
|
||||
const role = userClub.role || 'member';
|
||||
|
||||
// Get role-based permissions
|
||||
const rolePermissions = ROLE_PERMISSIONS[role] || ROLE_PERMISSIONS.member;
|
||||
|
||||
// Merge with custom permissions if any
|
||||
const customPermissions = userClub.permissions || {};
|
||||
const mergedPermissions = this.mergePermissions(rolePermissions, customPermissions);
|
||||
|
||||
return {
|
||||
role: role,
|
||||
isOwner: false,
|
||||
permissions: mergedPermissions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has specific permission
|
||||
*/
|
||||
async hasPermission(userId, clubId, resource, action) {
|
||||
const userPermissions = await this.getUserClubPermissions(userId, clubId);
|
||||
|
||||
if (!userPermissions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Owner always has permission
|
||||
if (userPermissions.isOwner) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// MyTischtennis settings are accessible to all approved members
|
||||
if (resource === 'mytischtennis') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const resourcePermissions = userPermissions.permissions[resource];
|
||||
if (!resourcePermissions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return resourcePermissions[action] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user role in club
|
||||
*/
|
||||
async setUserRole(userId, clubId, role, updatedByUserId) {
|
||||
// Check if updater has permission
|
||||
const canManagePermissions = await this.hasPermission(updatedByUserId, clubId, 'permissions', 'write');
|
||||
if (!canManagePermissions) {
|
||||
throw new Error('Keine Berechtigung zum Ändern von Rollen');
|
||||
}
|
||||
|
||||
// Check if target user is owner
|
||||
const targetUserClub = await UserClub.findOne({
|
||||
where: { userId, clubId }
|
||||
});
|
||||
|
||||
if (!targetUserClub) {
|
||||
throw new Error('Benutzer ist kein Mitglied dieses Clubs');
|
||||
}
|
||||
|
||||
if (targetUserClub.isOwner) {
|
||||
throw new Error('Die Rolle des Club-Erstellers kann nicht geändert werden');
|
||||
}
|
||||
|
||||
// Validate role
|
||||
if (!ROLE_PERMISSIONS[role]) {
|
||||
throw new Error('Ungültige Rolle');
|
||||
}
|
||||
|
||||
await targetUserClub.update({ role });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Rolle erfolgreich aktualisiert'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom permissions for user
|
||||
*/
|
||||
async setCustomPermissions(userId, clubId, customPermissions, updatedByUserId) {
|
||||
// Check if updater has permission
|
||||
const canManagePermissions = await this.hasPermission(updatedByUserId, clubId, 'permissions', 'write');
|
||||
if (!canManagePermissions) {
|
||||
throw new Error('Keine Berechtigung zum Ändern von Berechtigungen');
|
||||
}
|
||||
|
||||
// Check if target user is owner
|
||||
const targetUserClub = await UserClub.findOne({
|
||||
where: { userId, clubId }
|
||||
});
|
||||
|
||||
if (!targetUserClub) {
|
||||
throw new Error('Benutzer ist kein Mitglied dieses Clubs');
|
||||
}
|
||||
|
||||
if (targetUserClub.isOwner) {
|
||||
throw new Error('Die Berechtigungen des Club-Erstellers können nicht geändert werden');
|
||||
}
|
||||
|
||||
await targetUserClub.update({ permissions: customPermissions });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Berechtigungen erfolgreich aktualisiert'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user status (activate/deactivate)
|
||||
*/
|
||||
async setUserStatus(userId, clubId, approved, updatedByUserId) {
|
||||
// Check if updater has permission
|
||||
const canManagePermissions = await this.hasPermission(updatedByUserId, clubId, 'permissions', 'write');
|
||||
if (!canManagePermissions) {
|
||||
throw new Error('Keine Berechtigung zum Ändern des Status');
|
||||
}
|
||||
|
||||
// Check if target user is owner
|
||||
const targetUserClub = await UserClub.findOne({
|
||||
where: { userId, clubId }
|
||||
});
|
||||
|
||||
if (!targetUserClub) {
|
||||
throw new Error('Benutzer ist kein Mitglied dieses Clubs');
|
||||
}
|
||||
|
||||
if (targetUserClub.isOwner) {
|
||||
throw new Error('Der Status des Club-Erstellers kann nicht geändert werden');
|
||||
}
|
||||
|
||||
await targetUserClub.update({ approved });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: approved ? 'Benutzer erfolgreich aktiviert' : 'Benutzer erfolgreich deaktiviert'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all club members with their permissions
|
||||
*/
|
||||
async getClubMembersWithPermissions(clubId, requestingUserId) {
|
||||
// Check if requester has permission to read permissions
|
||||
const canReadPermissions = await this.hasPermission(requestingUserId, clubId, 'permissions', 'read');
|
||||
if (!canReadPermissions) {
|
||||
throw new Error('Keine Berechtigung zum Anzeigen von Berechtigungen');
|
||||
}
|
||||
|
||||
const userClubs = await UserClub.findAll({
|
||||
where: {
|
||||
clubId
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'email']
|
||||
}]
|
||||
});
|
||||
|
||||
return userClubs.map(uc => ({
|
||||
userId: uc.userId,
|
||||
user: uc.user,
|
||||
role: uc.role,
|
||||
isOwner: uc.isOwner,
|
||||
approved: uc.approved,
|
||||
permissions: uc.permissions,
|
||||
effectivePermissions: this.getEffectivePermissions(uc)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get effective permissions (role + custom)
|
||||
*/
|
||||
getEffectivePermissions(userClub) {
|
||||
if (userClub.isOwner) {
|
||||
return ROLE_PERMISSIONS.admin;
|
||||
}
|
||||
|
||||
const rolePermissions = ROLE_PERMISSIONS[userClub.role] || ROLE_PERMISSIONS.member;
|
||||
const customPermissions = userClub.permissions || {};
|
||||
|
||||
return this.mergePermissions(rolePermissions, customPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge role permissions with custom permissions
|
||||
*/
|
||||
mergePermissions(rolePermissions, customPermissions) {
|
||||
const merged = { ...rolePermissions };
|
||||
|
||||
for (const resource in customPermissions) {
|
||||
if (!merged[resource]) {
|
||||
merged[resource] = {};
|
||||
}
|
||||
merged[resource] = {
|
||||
...merged[resource],
|
||||
...customPermissions[resource]
|
||||
};
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark user as club owner (used when creating a club)
|
||||
*/
|
||||
async setClubOwner(userId, clubId) {
|
||||
const userClub = await UserClub.findOne({
|
||||
where: { userId, clubId }
|
||||
});
|
||||
|
||||
if (!userClub) {
|
||||
throw new Error('UserClub relationship not found');
|
||||
}
|
||||
|
||||
await userClub.update({
|
||||
isOwner: true,
|
||||
role: 'admin',
|
||||
approved: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available roles
|
||||
*/
|
||||
getAvailableRoles() {
|
||||
return [
|
||||
{ value: 'admin', label: 'Administrator', description: 'Vollzugriff auf alle Funktionen' },
|
||||
{ value: 'trainer', label: 'Trainer', description: 'Kann Trainingseinheiten, Mitglieder und Teams verwalten' },
|
||||
{ value: 'team_manager', label: 'Mannschaftsführer', description: 'Kann Teams und Spielpläne verwalten' },
|
||||
{ value: 'tournament_manager', label: 'Turnierleiter', description: 'Kann Turniere verwalten' },
|
||||
{ value: 'member', label: 'Mitglied', description: 'Kann nur Trainings-Statistiken ansehen' }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permission structure for frontend
|
||||
*/
|
||||
getPermissionStructure() {
|
||||
return {
|
||||
diary: { label: 'Trainingstagebuch', actions: ['read', 'write', 'delete'] },
|
||||
members: { label: 'Mitglieder', actions: ['read', 'write', 'delete'] },
|
||||
teams: { label: 'Teams', actions: ['read', 'write', 'delete'] },
|
||||
schedule: { label: 'Spielpläne', actions: ['read', 'write', 'delete'] },
|
||||
tournaments: { label: 'Turniere', actions: ['read', 'write', 'delete'] },
|
||||
statistics: { label: 'Statistiken', actions: ['read', 'write'] },
|
||||
settings: { label: 'Einstellungen', actions: ['read', 'write'] },
|
||||
permissions: { label: 'Berechtigungsverwaltung', actions: ['read', 'write'] },
|
||||
approvals: { label: 'Freigaben (Mitgliedsanträge)', actions: ['read', 'write'] },
|
||||
mytischtennis_admin: { label: 'MyTischtennis Admin', actions: ['read', 'write'] },
|
||||
predefined_activities: { label: 'Vordefinierte Aktivitäten', actions: ['read', 'write', 'delete'] }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new PermissionService();
|
||||
|
||||
@@ -11,7 +11,7 @@ class SchedulerService {
|
||||
|
||||
/**
|
||||
* Start the scheduler
|
||||
*/
|
||||
*/
|
||||
start() {
|
||||
if (this.isRunning) {
|
||||
devLog('Scheduler is already running');
|
||||
|
||||
Reference in New Issue
Block a user