added user and roles controllers and unified naming

This commit is contained in:
root
2025-12-25 11:49:45 -05:00
parent 3d6aa856b4
commit 9f19ec1302
11 changed files with 833 additions and 23 deletions

View File

@@ -5,7 +5,7 @@ namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\Module\ModuleManager;
use KTXC\Security\Authorization\PermissionChecker;
use KTXC\Service\UserService;
use KTXC\Service\UserAccountsService;
use KTXC\SessionIdentity;
use KTXF\Controller\ControllerAbstract;
use KTXC\SessionTenant;
@@ -17,7 +17,7 @@ class InitController extends ControllerAbstract
private readonly SessionTenant $tenant,
private readonly SessionIdentity $userIdentity,
private readonly ModuleManager $moduleManager,
private readonly UserService $userService,
private readonly UserAccountsService $userService,
private readonly PermissionChecker $permissionChecker,
) {}

View File

@@ -0,0 +1,251 @@
<?php
namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\Service\UserAccountsService;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXF\Controller\ControllerAbstract;
use KTXF\Routing\Attributes\AuthenticatedRoute;
use Psr\Log\LoggerInterface;
/**
* User Accounts Controller
* Core administrative user management operations
*/
class UserAccountsController extends ControllerAbstract
{
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private readonly UserAccountsService $userService,
private readonly LoggerInterface $logger
) {}
/**
* Main versioned endpoint for user management
*/
#[AuthenticatedRoute('/user/accounts/v1', name: 'user.accounts.v1', methods: ['POST'])]
public function index(int $version, string $transaction, string $operation, array $data = []): JsonResponse
{
try {
// Check admin permission
if (!$this->userIdentity->hasPermission('user.admin')) {
return new JsonResponse([
'status' => 'error',
'data' => ['code' => 403, 'message' => 'Insufficient permissions']
], JsonResponse::HTTP_FORBIDDEN);
}
$result = $this->process($operation, $data);
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'success',
'data' => $result,
], JsonResponse::HTTP_OK);
} catch (\InvalidArgumentException $e) {
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'error',
'data' => ['code' => 400, 'message' => $e->getMessage()]
], JsonResponse::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
$this->logger->error('User manager operation failed', [
'operation' => $operation,
'error' => $e->getMessage()
]);
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'error',
'data' => ['code' => $e->getCode(), 'message' => $e->getMessage()]
], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Process operation
*/
private function process(string $operation, array $data): mixed
{
return match ($operation) {
'user.list' => $this->userList($data),
'user.fetch' => $this->userFetch($data),
'user.create' => $this->userCreate($data),
'user.update' => $this->userUpdate($data),
'user.delete' => $this->userDelete($data),
'user.provider.unlink' => $this->userProviderUnlink($data),
default => throw new \InvalidArgumentException("Invalid operation: {$operation}"),
};
}
// =========================================================================
// User Operations
// =========================================================================
/**
* List all users for tenant
*/
private function userList(array $data): array
{
return $this->userService->listUsers($data);
}
/**
* Fetch single user by UID
*/
private function userFetch(array $data): array
{
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
$user = $this->userService->fetchByIdentifier($uid);
if (!$user) {
throw new \InvalidArgumentException('User not found');
}
// Get editable fields for profile
$editableFields = $this->userService->getEditableFields($uid);
$user['profile_editable'] = $editableFields;
return $user;
}
/**
* Create new user
*/
private function userCreate(array $data): array
{
if (!$this->userIdentity->hasPermission('user.create')) {
throw new \InvalidArgumentException('Insufficient permissions to create users');
}
$userData = [
'identity' => $data['identity'] ?? throw new \InvalidArgumentException('Identity required'),
'label' => $data['label'] ?? $data['identity'],
'enabled' => $data['enabled'] ?? true,
'roles' => $data['roles'] ?? [],
'profile' => $data['profile'] ?? [],
'settings' => [],
'provider' => null,
'provider_subject' => null,
'provider_managed_fields' => []
];
$this->logger->info('Creating user', [
'tenant' => $this->tenantIdentity->identifier(),
'identity' => $userData['identity'],
'actor' => $this->userIdentity->identifier()
]);
return $this->userService->createUser($userData);
}
/**
* Update existing user
*/
private function userUpdate(array $data): bool
{
if (!$this->userIdentity->hasPermission('user.update')) {
throw new \InvalidArgumentException('Insufficient permissions to update users');
}
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
// Build updates (exclude sensitive fields)
$updates = [];
$allowedFields = ['label', 'enabled', 'roles', 'profile'];
foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$updates[$field] = $data[$field];
}
}
if (empty($updates)) {
throw new \InvalidArgumentException('No valid fields to update');
}
// Special handling for profile updates (respect managed fields)
if (isset($updates['profile'])) {
$user = $this->userService->fetchByIdentifier($uid);
$managedFields = $user['provider_managed_fields'] ?? [];
foreach ($managedFields as $field) {
unset($updates['profile'][$field]);
}
}
$this->logger->info('Updating user', [
'tenant' => $this->tenantIdentity->identifier(),
'uid' => $uid,
'actor' => $this->userIdentity->identifier()
]);
return $this->userService->updateUser($uid, $updates);
}
/**
* Delete user
*/
private function userDelete(array $data): bool
{
if (!$this->userIdentity->hasPermission('user.delete')) {
throw new \InvalidArgumentException('Insufficient permissions to delete users');
}
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
// Prevent self-deletion
if ($uid === $this->userIdentity->identifier()) {
throw new \InvalidArgumentException('Cannot delete your own account');
}
$this->logger->info('Deleting user', [
'tenant' => $this->tenantIdentity->identifier(),
'uid' => $uid,
'actor' => $this->userIdentity->identifier()
]);
return $this->userService->deleteUser($uid);
}
// =========================================================================
// Security Operations
// =========================================================================
/**
* Unlink external provider
*/
private function userProviderUnlink(array $data): bool
{
if (!$this->userIdentity->hasPermission('user.admin')) {
throw new \InvalidArgumentException('Insufficient permissions');
}
$uid = $data['uid'] ?? throw new \InvalidArgumentException('User ID required');
$updates = [
'provider' => null,
'provider_subject' => null,
'provider_managed_fields' => []
];
$this->logger->info('Unlinking provider', [
'tenant' => $this->tenantIdentity->identifier(),
'uid' => $uid,
'actor' => $this->userIdentity->identifier()
]);
return $this->userService->updateUser($uid, $updates);
}
}

View File

@@ -3,7 +3,7 @@
namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\Service\UserService;
use KTXC\Service\UserAccountsService;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXF\Controller\ControllerAbstract;
@@ -14,7 +14,7 @@ class UserProfileController extends ControllerAbstract
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private readonly UserService $userService
private readonly UserAccountsService $userService
) {}
/**

View File

@@ -0,0 +1,201 @@
<?php
namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXC\Service\UserRolesService;
use KTXF\Controller\ControllerAbstract;
use KTXF\Routing\Attributes\AuthenticatedRoute;
use Psr\Log\LoggerInterface;
/**
* User Roles Controller
* Core administrative role management operations
*/
class UserRolesController extends ControllerAbstract
{
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private readonly UserRolesService $roleService,
private readonly LoggerInterface $logger
) {}
/**
* Main versioned endpoint for role management
*/
#[AuthenticatedRoute('/user/roles/v1', name: 'user.roles.v1', methods: ['POST'])]
public function index(int $version, string $transaction, string $operation, array $data = []): JsonResponse
{
try {
// Check role admin permission
if (!$this->userIdentity->hasPermission('role.admin')) {
return new JsonResponse([
'status' => 'error',
'data' => ['code' => 403, 'message' => 'Insufficient permissions']
], JsonResponse::HTTP_FORBIDDEN);
}
$result = $this->process($operation, $data);
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'success',
'data' => $result,
], JsonResponse::HTTP_OK);
} catch (\InvalidArgumentException $e) {
$this->logger->error('Role manager validation error', [
'operation' => $operation,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'error',
'data' => ['code' => 400, 'message' => $e->getMessage()]
], JsonResponse::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
$this->logger->error('Role manager operation failed', [
'operation' => $operation,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'error',
'data' => ['code' => $e->getCode(), 'message' => $e->getMessage()]
], JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* Process operation
*/
private function process(string $operation, array $data): mixed
{
return match ($operation) {
'role.list' => $this->roleList($data),
'role.fetch' => $this->roleFetch($data),
'role.create' => $this->roleCreate($data),
'role.update' => $this->roleUpdate($data),
'role.delete' => $this->roleDelete($data),
'permissions.list' => $this->permissionsList($data),
default => throw new \InvalidArgumentException("Invalid operation: {$operation}"),
};
}
// =========================================================================
// Role Operations
// =========================================================================
/**
* List all roles
*/
private function roleList(array $data): array
{
$roles = $this->roleService->listRoles();
// Add user count to each role
foreach ($roles as &$role) {
$role['user_count'] = $this->roleService->getRoleUserCount($role['rid']);
}
return $roles;
}
/**
* Fetch single role
*/
private function roleFetch(array $data): array
{
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
$role = $this->roleService->getRole($rid);
if (!$role) {
throw new \InvalidArgumentException('Role not found');
}
$role['user_count'] = $this->roleService->getRoleUserCount($rid);
return $role;
}
/**
* Create new role
*/
private function roleCreate(array $data): array
{
if (!$this->userIdentity->hasPermission('role.manage')) {
throw new \InvalidArgumentException('Insufficient permissions to create roles');
}
$roleData = [
'label' => $data['label'] ?? throw new \InvalidArgumentException('Role label required'),
'description' => $data['description'] ?? '',
'permissions' => $data['permissions'] ?? []
];
return $this->roleService->createRole($roleData);
}
/**
* Update existing role
*/
private function roleUpdate(array $data): bool
{
if (!$this->userIdentity->hasPermission('role.manage')) {
throw new \InvalidArgumentException('Insufficient permissions to update roles');
}
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
$updates = [];
$allowedFields = ['label', 'description', 'permissions'];
foreach ($allowedFields as $field) {
if (isset($data[$field])) {
$updates[$field] = $data[$field];
}
}
if (empty($updates)) {
throw new \InvalidArgumentException('No valid fields to update');
}
return $this->roleService->updateRole($rid, $updates);
}
/**
* Delete role
*/
private function roleDelete(array $data): bool
{
if (!$this->userIdentity->hasPermission('role.manage')) {
throw new \InvalidArgumentException('Insufficient permissions to delete roles');
}
$rid = $data['rid'] ?? throw new \InvalidArgumentException('Role ID required');
return $this->roleService->deleteRole($rid);
}
/**
* Get available permissions
*/
private function permissionsList(array $data): array
{
return $this->roleService->availablePermissions();
}
}

View File

@@ -3,7 +3,7 @@
namespace KTXC\Controllers;
use KTXC\Http\Response\JsonResponse;
use KTXC\Service\UserService;
use KTXC\Service\UserAccountsService;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXF\Controller\ControllerAbstract;
@@ -14,7 +14,7 @@ class UserSettingsController extends ControllerAbstract
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private readonly UserService $userService
private readonly UserAccountsService $userService
) {}
/**