// SPDX-License-Identifier: AGPL-3.0-or-later namespace OCA\Forum\Controller; use OCA\Forum\Attribute\RequirePermission; use OCA\Forum\Db\RoleMapper; use OCA\Forum\Db\UserRoleMapper; use OCA\Forum\Service\UserRoleService; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; use OCP\IRequest; use Psr\Log\LoggerInterface; class UserRoleController extends OCSController { public function __construct( string $appName, IRequest $request, private UserRoleMapper $userRoleMapper, private RoleMapper $roleMapper, private UserRoleService $userRoleService, private LoggerInterface $logger, ) { parent::__construct($appName, $request); } /** * Get user role assignments for a user * * Returns Role objects enriched with userRoleId for managing role assignments. * * @param string $userId Nextcloud user ID * @param int<1, 100> $limit Maximum number of role assignments to return * @param int<0, max> $offset Offset for pagination * @return DataResponse>, array{}> * * 200: User role assignments returned */ #[NoAdminRequired] #[RequirePermission('canAccessAdminTools')] #[ApiRoute(verb: 'GET', url: '/api/users/{userId}/roles')] public function byUser(string $userId, int $limit = 100, int $offset = 0): DataResponse { try { // Get UserRole entities to get the userRoleId for delete operations $userRoles = $this->userRoleMapper->findByUserId($userId); // Build response with full Role info + userRoleId $result = []; foreach ($userRoles as $userRole) { try { $role = $this->roleMapper->find($userRole->getRoleId()); $roleData = $role->jsonSerialize(); $roleData['userRoleId'] = $userRole->getId(); $result[] = $roleData; } catch (DoesNotExistException $e) { // Role was deleted, skip this user role continue; } } return new DataResponse(array_slice($result, $offset, $limit)); } catch (\Exception $e) { $this->logger->error('Error fetching user roles: ' . $e->getMessage()); return new DataResponse(['error' => 'Failed to fetch user roles'], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** * Get users with a specific role * * @param int $roleId Role ID * @param int<1, 200> $limit Maximum number of users to return * @param int<0, max> $offset Offset for pagination * @return DataResponse>, array{}> * * 200: User roles returned */ #[NoAdminRequired] #[RequirePermission('canAccessAdminTools')] #[ApiRoute(verb: 'GET', url: '/api/roles/{roleId}/users')] public function byRole(int $roleId, int $limit = 200, int $offset = 0): DataResponse { try { $userRoles = array_slice($this->userRoleMapper->findByRoleId($roleId), $offset, $limit); return new DataResponse(array_map(fn ($ur) => $ur->jsonSerialize(), $userRoles)); } catch (\Exception $e) { $this->logger->error('Error fetching users by role: ' . $e->getMessage()); return new DataResponse(['error' => 'Failed to fetch users by role'], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** * Assign a role to a user * * @param string $userId Nextcloud user ID * @param int $roleId Role ID * @return DataResponse, array{}>|DataResponse|DataResponse * * 201: Role assigned to user * 403: Cannot assign guest role to users * 409: User already has this role */ #[NoAdminRequired] #[RequirePermission('canEditRoles')] #[ApiRoute(verb: 'POST', url: '/api/user-roles')] public function create(string $userId, int $roleId): DataResponse { try { // Check if the role is the guest role $role = $this->roleMapper->find($roleId); if ($role->getRoleType() === \OCA\Forum\Db\Role::ROLE_TYPE_GUEST) { return new DataResponse(['error' => 'Guest role cannot be assigned to users'], Http::STATUS_FORBIDDEN); } // Check if user already has the role if ($this->userRoleService->hasRole($userId, $roleId)) { return new DataResponse(['error' => 'User already has this role'], Http::STATUS_CONFLICT); } // Assign the role using the service $createdUserRole = $this->userRoleService->assignRole($userId, $roleId, skipIfExists: false); if ($createdUserRole === null) { // This shouldn't happen since we checked hasRole above, but handle it just in case return new DataResponse(['error' => 'Failed to assign role'], Http::STATUS_INTERNAL_SERVER_ERROR); } return new DataResponse($createdUserRole->jsonSerialize(), Http::STATUS_CREATED); } catch (DoesNotExistException $e) { return new DataResponse(['error' => 'Role not found'], Http::STATUS_NOT_FOUND); } catch (\Exception $e) { $this->logger->error('Error assigning role to user: ' . $e->getMessage()); return new DataResponse(['error' => 'Failed to assign role to user'], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** * Remove a role from a user * * @param int $id User role ID * @return DataResponse|DataResponse * * 200: Role removed from user * 403: Cannot remove guest role from users */ #[NoAdminRequired] #[RequirePermission('canEditRoles')] #[ApiRoute(verb: 'DELETE', url: '/api/user-roles/{id}')] public function destroy(int $id): DataResponse { try { $userRole = $this->userRoleMapper->find($id); // Check if the role being removed is the guest role $role = $this->roleMapper->find($userRole->getRoleId()); if ($role->getRoleType() === \OCA\Forum\Db\Role::ROLE_TYPE_GUEST) { return new DataResponse(['error' => 'Guest role cannot be removed from users'], Http::STATUS_FORBIDDEN); } $this->userRoleMapper->delete($userRole); return new DataResponse(['success' => true]); } catch (DoesNotExistException $e) { return new DataResponse(['error' => 'User role not found'], Http::STATUS_NOT_FOUND); } catch (\Exception $e) { $this->logger->error('Error removing role from user: ' . $e->getMessage()); return new DataResponse(['error' => 'Failed to remove role from user'], Http::STATUS_INTERNAL_SERVER_ERROR); } } }