From f4ce9f33786e3b98ef4fe7264108d93cb7d7aa33 Mon Sep 17 00:00:00 2001 From: fs Date: Fri, 13 Mar 2026 21:58:51 +0100 Subject: [PATCH] docs: add class docblocks, business-rule comments, and transaction wrapper - Add single-line class docblocks to all 59 repository classes and interfaces describing scope and responsibility - Add multi-line docblocks to key services documenting business rules: AuthService (6-step login cascade), ImportService (3-phase CSV workflow), TenantScopeService (strict/permissive modes), PermissionService (RBAC resolution + two-tier caching), UserAccountService (atomicity + audit) - Add transaction(callable) wrapper to DatabaseSessionRepository to DRY up begin/commit/rollback boilerplate Co-Authored-By: Claude Opus 4.6 --- .../Access/PermissionRepository.php | 1 + .../Access/PermissionRepositoryInterface.php | 1 + .../Access/RoleAssignableRoleRepository.php | 1 + .../Access/RolePermissionRepository.php | 1 + .../RolePermissionRepositoryInterface.php | 1 + lib/Repository/Access/RoleRepository.php | 1 + .../Access/RoleRepositoryInterface.php | 1 + lib/Repository/Access/UserRoleRepository.php | 1 + .../Access/UserRoleRepositoryInterface.php | 1 + .../Audit/ApiAuditLogRepository.php | 1 + .../Audit/ApiAuditLogRepositoryInterface.php | 1 + .../Audit/ImportAuditRunRepository.php | 1 + .../ImportAuditRunRepositoryInterface.php | 1 + .../Audit/SystemAuditLogRepository.php | 1 + .../SystemAuditLogRepositoryInterface.php | 1 + .../Audit/UserLifecycleAuditRepository.php | 1 + .../UserLifecycleAuditRepositoryInterface.php | 1 + lib/Repository/Auth/ApiTokenRepository.php | 1 + .../Auth/ApiTokenRepositoryInterface.php | 1 + .../Auth/EmailVerificationRepository.php | 1 + .../EmailVerificationRepositoryInterface.php | 1 + .../Auth/PasswordResetRepository.php | 1 + .../Auth/PasswordResetRepositoryInterface.php | 1 + .../Auth/RememberTokenRepository.php | 1 + .../Auth/RememberTokenRepositoryInterface.php | 1 + .../Content/PageContentRepository.php | 1 + lib/Repository/Content/PageRepository.php | 1 + .../TenantCustomFieldDefinitionRepository.php | 1 + .../TenantCustomFieldOptionRepository.php | 1 + .../UserCustomFieldValueOptionRepository.php | 1 + .../UserCustomFieldValueRepository.php | 1 + lib/Repository/Mail/MailLogRepository.php | 1 + .../Mail/MailLogRepositoryInterface.php | 1 + lib/Repository/Org/DepartmentRepository.php | 1 + .../Org/DepartmentRepositoryInterface.php | 1 + .../Org/UserDepartmentRepository.php | 1 + .../Org/UserDepartmentRepositoryInterface.php | 1 + .../Scheduler/ScheduledJobRepository.php | 1 + .../ScheduledJobRepositoryInterface.php | 1 + .../Scheduler/ScheduledJobRunRepository.php | 1 + .../ScheduledJobRunRepositoryInterface.php | 1 + .../Scheduler/SchedulerRuntimeRepository.php | 1 + .../SchedulerRuntimeRepositoryInterface.php | 1 + .../Security/RateLimitRepository.php | 1 + .../Security/RateLimitRepositoryInterface.php | 1 + lib/Repository/Settings/SettingRepository.php | 1 + .../Settings/SettingRepositoryInterface.php | 1 + .../Support/DatabaseSessionRepository.php | 30 +++++++++++++++++++ .../DatabaseSessionRepositoryInterface.php | 8 +++++ lib/Repository/Support/RepoQuery.php | 1 + .../Tenant/TenantMicrosoftAuthRepository.php | 1 + ...TenantMicrosoftAuthRepositoryInterface.php | 1 + lib/Repository/Tenant/TenantRepository.php | 1 + .../Tenant/TenantRepositoryInterface.php | 1 + .../Tenant/UserTenantRepository.php | 1 + .../Tenant/UserTenantRepositoryInterface.php | 1 + .../User/UserExternalIdentityRepository.php | 1 + ...serExternalIdentityRepositoryInterface.php | 1 + .../User/UserListQueryRepository.php | 1 + .../User/UserListQueryRepositoryInterface.php | 1 + lib/Repository/User/UserReadRepository.php | 1 + .../User/UserReadRepositoryInterface.php | 1 + .../User/UserSavedFilterRepository.php | 1 + .../UserSavedFilterRepositoryInterface.php | 1 + lib/Repository/User/UserWriteRepository.php | 1 + .../User/UserWriteRepositoryInterface.php | 1 + lib/Service/Access/PermissionService.php | 12 ++++++++ lib/Service/Auth/AuthService.php | 23 ++++++++++++++ lib/Service/Import/ImportService.php | 17 +++++++++++ lib/Service/Tenant/TenantScopeService.php | 13 ++++++++ lib/Service/User/UserAccountService.php | 14 +++++++++ 71 files changed, 181 insertions(+) diff --git a/lib/Repository/Access/PermissionRepository.php b/lib/Repository/Access/PermissionRepository.php index 3bb52f5..16b8579 100644 --- a/lib/Repository/Access/PermissionRepository.php +++ b/lib/Repository/Access/PermissionRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Reads and writes permission records; supports active/system flag filtering. */ class PermissionRepository implements PermissionRepositoryInterface { public function list(): array diff --git a/lib/Repository/Access/PermissionRepositoryInterface.php b/lib/Repository/Access/PermissionRepositoryInterface.php index bea74a0..5ac2eda 100644 --- a/lib/Repository/Access/PermissionRepositoryInterface.php +++ b/lib/Repository/Access/PermissionRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Access; +/** Contract for permission CRUD and lookup by key or ID. */ interface PermissionRepositoryInterface { public function list(): array; diff --git a/lib/Repository/Access/RoleAssignableRoleRepository.php b/lib/Repository/Access/RoleAssignableRoleRepository.php index e5acf85..45ba0a7 100644 --- a/lib/Repository/Access/RoleAssignableRoleRepository.php +++ b/lib/Repository/Access/RoleAssignableRoleRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Access; use MintyPHP\DB; +/** Manages which roles a given role is allowed to assign (role hierarchy). */ class RoleAssignableRoleRepository implements RoleAssignableRoleRepositoryInterface { public function listAssignableRoleIdsByRoleId(int $roleId): array diff --git a/lib/Repository/Access/RolePermissionRepository.php b/lib/Repository/Access/RolePermissionRepository.php index 3069421..5eee278 100644 --- a/lib/Repository/Access/RolePermissionRepository.php +++ b/lib/Repository/Access/RolePermissionRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Access; use MintyPHP\DB; +/** Manages the many-to-many link between roles and permissions. */ class RolePermissionRepository implements RolePermissionRepositoryInterface { public function listPermissionIdsByRoleId(int $roleId): array diff --git a/lib/Repository/Access/RolePermissionRepositoryInterface.php b/lib/Repository/Access/RolePermissionRepositoryInterface.php index 55ec1ef..b08ceb9 100644 --- a/lib/Repository/Access/RolePermissionRepositoryInterface.php +++ b/lib/Repository/Access/RolePermissionRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Access; +/** Contract for managing permission assignments on roles. */ interface RolePermissionRepositoryInterface { public function listPermissionIdsByRoleId(int $roleId): array; diff --git a/lib/Repository/Access/RoleRepository.php b/lib/Repository/Access/RoleRepository.php index 1ac744b..5f7a63a 100644 --- a/lib/Repository/Access/RoleRepository.php +++ b/lib/Repository/Access/RoleRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Reads and writes role records with pagination, search, and code uniqueness checks. */ class RoleRepository implements RoleRepositoryInterface { public function list(): array diff --git a/lib/Repository/Access/RoleRepositoryInterface.php b/lib/Repository/Access/RoleRepositoryInterface.php index e3d3e38..b24a190 100644 --- a/lib/Repository/Access/RoleRepositoryInterface.php +++ b/lib/Repository/Access/RoleRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Access; +/** Contract for role CRUD, existence checks, and filtered listing. */ interface RoleRepositoryInterface { public function list(): array; diff --git a/lib/Repository/Access/UserRoleRepository.php b/lib/Repository/Access/UserRoleRepository.php index e54fa51..3486ad7 100644 --- a/lib/Repository/Access/UserRoleRepository.php +++ b/lib/Repository/Access/UserRoleRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\Access; use MintyPHP\DB; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Queries user-role assignments and counts active/inactive users per role. */ class UserRoleRepository implements UserRoleRepositoryInterface { public function listRoleIdsByUserId(int $userId): array diff --git a/lib/Repository/Access/UserRoleRepositoryInterface.php b/lib/Repository/Access/UserRoleRepositoryInterface.php index 0f7e8f0..55f43c8 100644 --- a/lib/Repository/Access/UserRoleRepositoryInterface.php +++ b/lib/Repository/Access/UserRoleRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Access; +/** Contract for querying user-role relationships and role usage counts. */ interface UserRoleRepositoryInterface { public function listRoleIdsByUserId(int $userId): array; diff --git a/lib/Repository/Audit/ApiAuditLogRepository.php b/lib/Repository/Audit/ApiAuditLogRepository.php index f3e852c..13d8e02 100644 --- a/lib/Repository/Audit/ApiAuditLogRepository.php +++ b/lib/Repository/Audit/ApiAuditLogRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\Audit; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Records API request/response audit entries with status codes, timing, and error details. */ class ApiAuditLogRepository implements ApiAuditLogRepositoryInterface { private const FILTER_OPTIONS_LIMIT_MAX = 200; diff --git a/lib/Repository/Audit/ApiAuditLogRepositoryInterface.php b/lib/Repository/Audit/ApiAuditLogRepositoryInterface.php index 4f09ddb..00896ba 100644 --- a/lib/Repository/Audit/ApiAuditLogRepositoryInterface.php +++ b/lib/Repository/Audit/ApiAuditLogRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Audit; +/** Contract for API request audit log creation, pagination, and retention purge. */ interface ApiAuditLogRepositoryInterface { public function create(array $data): int|false; diff --git a/lib/Repository/Audit/ImportAuditRunRepository.php b/lib/Repository/Audit/ImportAuditRunRepository.php index f76a498..930826b 100644 --- a/lib/Repository/Audit/ImportAuditRunRepository.php +++ b/lib/Repository/Audit/ImportAuditRunRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Domain\Taxonomy\ImportAuditStatus; use MintyPHP\Repository\Support\RepoQuery; +/** Tracks CSV import runs including source file, row counts, status, and completion metadata. */ class ImportAuditRunRepository implements ImportAuditRunRepositoryInterface { private const FILTER_OPTIONS_LIMIT_MAX = 200; diff --git a/lib/Repository/Audit/ImportAuditRunRepositoryInterface.php b/lib/Repository/Audit/ImportAuditRunRepositoryInterface.php index 16587b8..7431461 100644 --- a/lib/Repository/Audit/ImportAuditRunRepositoryInterface.php +++ b/lib/Repository/Audit/ImportAuditRunRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Audit; +/** Contract for CSV import run audit trail creation, completion, and purge. */ interface ImportAuditRunRepositoryInterface { public function createRunning(array $data): int|false; diff --git a/lib/Repository/Audit/SystemAuditLogRepository.php b/lib/Repository/Audit/SystemAuditLogRepository.php index 26436fa..147577e 100644 --- a/lib/Repository/Audit/SystemAuditLogRepository.php +++ b/lib/Repository/Audit/SystemAuditLogRepository.php @@ -7,6 +7,7 @@ use MintyPHP\Domain\Taxonomy\SystemAuditChannel; use MintyPHP\Domain\Taxonomy\SystemAuditOutcome; use MintyPHP\Repository\Support\RepoQuery; +/** Persists system audit events with actor, target, outcome, channel, and hashed request metadata. */ class SystemAuditLogRepository implements SystemAuditLogRepositoryInterface { private const FILTER_OPTIONS_LIMIT_MAX = 200; diff --git a/lib/Repository/Audit/SystemAuditLogRepositoryInterface.php b/lib/Repository/Audit/SystemAuditLogRepositoryInterface.php index 5d7ddf0..17c1615 100644 --- a/lib/Repository/Audit/SystemAuditLogRepositoryInterface.php +++ b/lib/Repository/Audit/SystemAuditLogRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Audit; +/** Contract for system-wide security event logging with channel and outcome filtering. */ interface SystemAuditLogRepositoryInterface { public function create(array $data): int|false; diff --git a/lib/Repository/Audit/UserLifecycleAuditRepository.php b/lib/Repository/Audit/UserLifecycleAuditRepository.php index 424ac29..9eacfea 100644 --- a/lib/Repository/Audit/UserLifecycleAuditRepository.php +++ b/lib/Repository/Audit/UserLifecycleAuditRepository.php @@ -8,6 +8,7 @@ use MintyPHP\Domain\Taxonomy\UserLifecycleStatus; use MintyPHP\Domain\Taxonomy\UserLifecycleTriggerType; use MintyPHP\Repository\Support\RepoQuery; +/** Records user lifecycle transitions (deactivation, deletion, restore) with reason codes and snapshots. */ class UserLifecycleAuditRepository implements UserLifecycleAuditRepositoryInterface { private const FILTER_OPTIONS_LIMIT_MAX = 200; diff --git a/lib/Repository/Audit/UserLifecycleAuditRepositoryInterface.php b/lib/Repository/Audit/UserLifecycleAuditRepositoryInterface.php index c98a73c..8780c54 100644 --- a/lib/Repository/Audit/UserLifecycleAuditRepositoryInterface.php +++ b/lib/Repository/Audit/UserLifecycleAuditRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Audit; +/** Contract for tracking user deactivation/deletion lifecycle events and restore eligibility. */ interface UserLifecycleAuditRepositoryInterface { public function create(array $row): int|false; diff --git a/lib/Repository/Auth/ApiTokenRepository.php b/lib/Repository/Auth/ApiTokenRepository.php index c72f761..f41c00e 100644 --- a/lib/Repository/Auth/ApiTokenRepository.php +++ b/lib/Repository/Auth/ApiTokenRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\Auth; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Persists API tokens with selector/hash pairs, expiry tracking, and usage timestamps. */ class ApiTokenRepository implements ApiTokenRepositoryInterface { private const UUID_REGEX = '/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i'; diff --git a/lib/Repository/Auth/ApiTokenRepositoryInterface.php b/lib/Repository/Auth/ApiTokenRepositoryInterface.php index 1cc3a3c..e0b38aa 100644 --- a/lib/Repository/Auth/ApiTokenRepositoryInterface.php +++ b/lib/Repository/Auth/ApiTokenRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Auth; +/** Contract for API bearer token creation, lookup by selector or UUID, and revocation. */ interface ApiTokenRepositoryInterface { public function isUuid(string $value): bool; diff --git a/lib/Repository/Auth/EmailVerificationRepository.php b/lib/Repository/Auth/EmailVerificationRepository.php index acb5ea2..ea7e5dd 100644 --- a/lib/Repository/Auth/EmailVerificationRepository.php +++ b/lib/Repository/Auth/EmailVerificationRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Auth; use MintyPHP\DB; +/** Stores email verification codes with attempt counters, expiry, and completion status. */ class EmailVerificationRepository implements EmailVerificationRepositoryInterface { public function create(int $userId, string $codeHash, string $expiresAt): ?int diff --git a/lib/Repository/Auth/EmailVerificationRepositoryInterface.php b/lib/Repository/Auth/EmailVerificationRepositoryInterface.php index 767f8a7..f988ad1 100644 --- a/lib/Repository/Auth/EmailVerificationRepositoryInterface.php +++ b/lib/Repository/Auth/EmailVerificationRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Auth; +/** Contract for email verification code lifecycle: creation, lookup, attempt tracking, and completion. */ interface EmailVerificationRepositoryInterface { public function create(int $userId, string $codeHash, string $expiresAt): ?int; diff --git a/lib/Repository/Auth/PasswordResetRepository.php b/lib/Repository/Auth/PasswordResetRepository.php index f3f7883..964b29c 100644 --- a/lib/Repository/Auth/PasswordResetRepository.php +++ b/lib/Repository/Auth/PasswordResetRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Auth; use MintyPHP\DB; +/** Stores password reset codes with attempt counters, expiry, and completion history. */ class PasswordResetRepository implements PasswordResetRepositoryInterface { private function unwrapList($rows): array diff --git a/lib/Repository/Auth/PasswordResetRepositoryInterface.php b/lib/Repository/Auth/PasswordResetRepositoryInterface.php index 1867254..111ed44 100644 --- a/lib/Repository/Auth/PasswordResetRepositoryInterface.php +++ b/lib/Repository/Auth/PasswordResetRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Auth; +/** Contract for password reset code lifecycle: creation, lookup, attempt tracking, and completion. */ interface PasswordResetRepositoryInterface { public function create(int $userId, string $codeHash, string $expiresAt): ?int; diff --git a/lib/Repository/Auth/RememberTokenRepository.php b/lib/Repository/Auth/RememberTokenRepository.php index a03e09d..0e2b0ab 100644 --- a/lib/Repository/Auth/RememberTokenRepository.php +++ b/lib/Repository/Auth/RememberTokenRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Auth; use MintyPHP\DB; +/** Manages persistent login tokens with rotation, family tracking, and admin-initiated expiry. */ class RememberTokenRepository implements RememberTokenRepositoryInterface { private function unwrapList($rows): array diff --git a/lib/Repository/Auth/RememberTokenRepositoryInterface.php b/lib/Repository/Auth/RememberTokenRepositoryInterface.php index b86ff2e..4547f8f 100644 --- a/lib/Repository/Auth/RememberTokenRepositoryInterface.php +++ b/lib/Repository/Auth/RememberTokenRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Auth; +/** Contract for remember-me token creation, rotation, expiry, and active session counting. */ interface RememberTokenRepositoryInterface { public function create(int $userId, string $selector, string $tokenHash, string $expiresAt): ?int; diff --git a/lib/Repository/Content/PageContentRepository.php b/lib/Repository/Content/PageContentRepository.php index 9aa03e6..a336225 100644 --- a/lib/Repository/Content/PageContentRepository.php +++ b/lib/Repository/Content/PageContentRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Content; use MintyPHP\DB; +/** Reads and writes locale-specific content blocks for CMS pages. */ class PageContentRepository { private static function unwrap(?array $row): ?array diff --git a/lib/Repository/Content/PageRepository.php b/lib/Repository/Content/PageRepository.php index 8b669d4..b946b21 100644 --- a/lib/Repository/Content/PageRepository.php +++ b/lib/Repository/Content/PageRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Content; use MintyPHP\DB; +/** Looks up CMS page metadata by slug. */ class PageRepository { private static function unwrap(?array $row): ?array diff --git a/lib/Repository/CustomField/TenantCustomFieldDefinitionRepository.php b/lib/Repository/CustomField/TenantCustomFieldDefinitionRepository.php index 515efa0..f709e10 100644 --- a/lib/Repository/CustomField/TenantCustomFieldDefinitionRepository.php +++ b/lib/Repository/CustomField/TenantCustomFieldDefinitionRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\CustomField; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Reads and writes tenant-scoped custom field definitions with type and pagination filtering. */ class TenantCustomFieldDefinitionRepository { private static function unwrap(?array $row): ?array diff --git a/lib/Repository/CustomField/TenantCustomFieldOptionRepository.php b/lib/Repository/CustomField/TenantCustomFieldOptionRepository.php index 7dbeb8a..a7be895 100644 --- a/lib/Repository/CustomField/TenantCustomFieldOptionRepository.php +++ b/lib/Repository/CustomField/TenantCustomFieldOptionRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\CustomField; use MintyPHP\DB; +/** Retrieves selectable options for tenant-scoped custom field definitions. */ class TenantCustomFieldOptionRepository { private static function unwrapList($rows): array diff --git a/lib/Repository/CustomField/UserCustomFieldValueOptionRepository.php b/lib/Repository/CustomField/UserCustomFieldValueOptionRepository.php index a5590ca..b52c576 100644 --- a/lib/Repository/CustomField/UserCustomFieldValueOptionRepository.php +++ b/lib/Repository/CustomField/UserCustomFieldValueOptionRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\CustomField; use MintyPHP\DB; +/** Manages selected option links for user custom field values (atomic replace). */ class UserCustomFieldValueOptionRepository { public static function replaceForValueId(int $valueId, array $optionIds): bool diff --git a/lib/Repository/CustomField/UserCustomFieldValueRepository.php b/lib/Repository/CustomField/UserCustomFieldValueRepository.php index 1a39ed3..b0d5958 100644 --- a/lib/Repository/CustomField/UserCustomFieldValueRepository.php +++ b/lib/Repository/CustomField/UserCustomFieldValueRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\CustomField; use MintyPHP\DB; +/** Reads and writes custom field values attached to individual users. */ class UserCustomFieldValueRepository { private static function unwrapList($rows): array diff --git a/lib/Repository/Mail/MailLogRepository.php b/lib/Repository/Mail/MailLogRepository.php index da02ab3..9cbb680 100644 --- a/lib/Repository/Mail/MailLogRepository.php +++ b/lib/Repository/Mail/MailLogRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Domain\Taxonomy\MailLogStatus; use MintyPHP\Repository\Support\RepoQuery; +/** Logs outgoing emails with queued/sent/failed status transitions and provider message IDs. */ class MailLogRepository implements MailLogRepositoryInterface { public function create(array $data): ?int diff --git a/lib/Repository/Mail/MailLogRepositoryInterface.php b/lib/Repository/Mail/MailLogRepositoryInterface.php index bc331a0..5a65a22 100644 --- a/lib/Repository/Mail/MailLogRepositoryInterface.php +++ b/lib/Repository/Mail/MailLogRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Mail; +/** Contract for outgoing email log creation, status updates, and paginated listing. */ interface MailLogRepositoryInterface { public function create(array $data): ?int; diff --git a/lib/Repository/Org/DepartmentRepository.php b/lib/Repository/Org/DepartmentRepository.php index f3b6aac..ed62a84 100644 --- a/lib/Repository/Org/DepartmentRepository.php +++ b/lib/Repository/Org/DepartmentRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Reads and writes department records with cost center, code tracking, and pagination. */ class DepartmentRepository implements DepartmentRepositoryInterface { public function list(): array diff --git a/lib/Repository/Org/DepartmentRepositoryInterface.php b/lib/Repository/Org/DepartmentRepositoryInterface.php index 18f184b..9376952 100644 --- a/lib/Repository/Org/DepartmentRepositoryInterface.php +++ b/lib/Repository/Org/DepartmentRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Org; +/** Contract for department CRUD, existence checks, and filtered listing. */ interface DepartmentRepositoryInterface { public function list(): array; diff --git a/lib/Repository/Org/UserDepartmentRepository.php b/lib/Repository/Org/UserDepartmentRepository.php index ea46648..5f7d599 100644 --- a/lib/Repository/Org/UserDepartmentRepository.php +++ b/lib/Repository/Org/UserDepartmentRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\Org; use MintyPHP\DB; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Queries user-department assignments and counts active/inactive users per department. */ class UserDepartmentRepository implements UserDepartmentRepositoryInterface { public function listDepartmentIdsByUserId(int $userId): array diff --git a/lib/Repository/Org/UserDepartmentRepositoryInterface.php b/lib/Repository/Org/UserDepartmentRepositoryInterface.php index 5d66986..7c45096 100644 --- a/lib/Repository/Org/UserDepartmentRepositoryInterface.php +++ b/lib/Repository/Org/UserDepartmentRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Org; +/** Contract for querying user-department membership and per-department user counts. */ interface UserDepartmentRepositoryInterface { public function listDepartmentIdsByUserId(int $userId): array; diff --git a/lib/Repository/Scheduler/ScheduledJobRepository.php b/lib/Repository/Scheduler/ScheduledJobRepository.php index 825831e..d5dc21a 100644 --- a/lib/Repository/Scheduler/ScheduledJobRepository.php +++ b/lib/Repository/Scheduler/ScheduledJobRepository.php @@ -6,6 +6,7 @@ use MintyPHP\DB; use MintyPHP\Domain\Taxonomy\ScheduledJobStatus; use MintyPHP\Repository\Support\RepoQuery; +/** Persists scheduled jobs with cron expressions, next-run calculation, and metadata updates. */ class ScheduledJobRepository implements ScheduledJobRepositoryInterface { public function create(array $data): int|false diff --git a/lib/Repository/Scheduler/ScheduledJobRepositoryInterface.php b/lib/Repository/Scheduler/ScheduledJobRepositoryInterface.php index 31e65ec..0e0071c 100644 --- a/lib/Repository/Scheduler/ScheduledJobRepositoryInterface.php +++ b/lib/Repository/Scheduler/ScheduledJobRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Scheduler; +/** Contract for scheduled job CRUD, due-job queries, and run status tracking. */ interface ScheduledJobRepositoryInterface { public function create(array $data): int|false; diff --git a/lib/Repository/Scheduler/ScheduledJobRunRepository.php b/lib/Repository/Scheduler/ScheduledJobRunRepository.php index 3069ada..71fe0e7 100644 --- a/lib/Repository/Scheduler/ScheduledJobRunRepository.php +++ b/lib/Repository/Scheduler/ScheduledJobRunRepository.php @@ -7,6 +7,7 @@ use MintyPHP\Domain\Taxonomy\ScheduledJobRunStatus; use MintyPHP\Domain\Taxonomy\ScheduledJobTriggerType; use MintyPHP\Repository\Support\RepoQuery; +/** Records individual job run results with duration, output, and retention-based purging. */ class ScheduledJobRunRepository implements ScheduledJobRunRepositoryInterface { public function create(array $data): int|false diff --git a/lib/Repository/Scheduler/ScheduledJobRunRepositoryInterface.php b/lib/Repository/Scheduler/ScheduledJobRunRepositoryInterface.php index 188b910..29d0339 100644 --- a/lib/Repository/Scheduler/ScheduledJobRunRepositoryInterface.php +++ b/lib/Repository/Scheduler/ScheduledJobRunRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Scheduler; +/** Contract for scheduled job run history: creation, pagination, and retention purge. */ interface ScheduledJobRunRepositoryInterface { public function create(array $data): int|false; diff --git a/lib/Repository/Scheduler/SchedulerRuntimeRepository.php b/lib/Repository/Scheduler/SchedulerRuntimeRepository.php index d8fe7e7..a9bbaf3 100644 --- a/lib/Repository/Scheduler/SchedulerRuntimeRepository.php +++ b/lib/Repository/Scheduler/SchedulerRuntimeRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\Scheduler; use MintyPHP\DB; use MintyPHP\Domain\Taxonomy\SchedulerRuntimeResult; +/** Tracks scheduler daemon heartbeat, result codes, and error state. */ class SchedulerRuntimeRepository implements SchedulerRuntimeRepositoryInterface { public function touchHeartbeat(string $result, ?string $errorCode = null, ?string $heartbeatAtUtc = null): bool diff --git a/lib/Repository/Scheduler/SchedulerRuntimeRepositoryInterface.php b/lib/Repository/Scheduler/SchedulerRuntimeRepositoryInterface.php index 7db46fd..2d394ac 100644 --- a/lib/Repository/Scheduler/SchedulerRuntimeRepositoryInterface.php +++ b/lib/Repository/Scheduler/SchedulerRuntimeRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Scheduler; +/** Contract for scheduler daemon heartbeat and runtime status. */ interface SchedulerRuntimeRepositoryInterface { public function touchHeartbeat(string $result, ?string $errorCode = null, ?string $heartbeatAtUtc = null): bool; diff --git a/lib/Repository/Security/RateLimitRepository.php b/lib/Repository/Security/RateLimitRepository.php index f5cabf8..bce8082 100644 --- a/lib/Repository/Security/RateLimitRepository.php +++ b/lib/Repository/Security/RateLimitRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Security; use MintyPHP\DB; +/** Persists rate limit counters with hit tracking, window expiry, and block duration. */ class RateLimitRepository implements RateLimitRepositoryInterface { public function findByScopeAndHash(string $scope, string $subjectHash): ?array diff --git a/lib/Repository/Security/RateLimitRepositoryInterface.php b/lib/Repository/Security/RateLimitRepositoryInterface.php index 2651857..9760f6d 100644 --- a/lib/Repository/Security/RateLimitRepositoryInterface.php +++ b/lib/Repository/Security/RateLimitRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Security; +/** Contract for sliding-window rate limit storage by scope and subject hash. */ interface RateLimitRepositoryInterface { public function findByScopeAndHash(string $scope, string $subjectHash): ?array; diff --git a/lib/Repository/Settings/SettingRepository.php b/lib/Repository/Settings/SettingRepository.php index 98a0088..0d6f543 100644 --- a/lib/Repository/Settings/SettingRepository.php +++ b/lib/Repository/Settings/SettingRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Settings; use MintyPHP\DB; +/** Reads and upserts application settings as key-value pairs in the database. */ class SettingRepository implements SettingRepositoryInterface { public function find(string $key): ?array diff --git a/lib/Repository/Settings/SettingRepositoryInterface.php b/lib/Repository/Settings/SettingRepositoryInterface.php index 9167502..15eddae 100644 --- a/lib/Repository/Settings/SettingRepositoryInterface.php +++ b/lib/Repository/Settings/SettingRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Settings; +/** Contract for key-value application settings with optional descriptions. */ interface SettingRepositoryInterface { public function find(string $key): ?array; diff --git a/lib/Repository/Support/DatabaseSessionRepository.php b/lib/Repository/Support/DatabaseSessionRepository.php index f0a6b40..7d26298 100644 --- a/lib/Repository/Support/DatabaseSessionRepository.php +++ b/lib/Repository/Support/DatabaseSessionRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Support; use MintyPHP\DB; +/** Executes database session operations: advisory locks, transactions, and commit/rollback. */ class DatabaseSessionRepository implements DatabaseSessionRepositoryInterface { public function acquireAdvisoryLock(string $lockName, int $timeoutSeconds = 0): bool @@ -47,4 +48,33 @@ class DatabaseSessionRepository implements DatabaseSessionRepositoryInterface { DB::handle()->rollback(); } + + /** + * Run a callback inside a DB transaction. Commits on success, rolls back on exception. + * + * Eliminates the need for manual begin/commit/rollback + rollbackQuietly() boilerplate + * that is duplicated across multiple services. The callback receives no arguments; + * use closures to capture dependencies. + * + * @template T + * @param callable(): T $callback + * @return T The value returned by the callback. + * @throws \Throwable Re-throws the original exception after rollback. + */ + public function transaction(callable $callback): mixed + { + $this->beginTransaction(); + try { + $result = $callback(); + $this->commitTransaction(); + return $result; + } catch (\Throwable $e) { + try { + $this->rollbackTransaction(); + } catch (\Throwable) { + // Swallow — the original exception is more important. + } + throw $e; + } + } } diff --git a/lib/Repository/Support/DatabaseSessionRepositoryInterface.php b/lib/Repository/Support/DatabaseSessionRepositoryInterface.php index 5bca570..514ee72 100644 --- a/lib/Repository/Support/DatabaseSessionRepositoryInterface.php +++ b/lib/Repository/Support/DatabaseSessionRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Support; +/** Contract for database-level session control: advisory locks and transaction management. */ interface DatabaseSessionRepositoryInterface { public function acquireAdvisoryLock(string $lockName, int $timeoutSeconds = 0): bool; @@ -13,4 +14,11 @@ interface DatabaseSessionRepositoryInterface public function commitTransaction(): void; public function rollbackTransaction(): void; + + /** + * @template T + * @param callable(): T $callback + * @return T + */ + public function transaction(callable $callback): mixed; } diff --git a/lib/Repository/Support/RepoQuery.php b/lib/Repository/Support/RepoQuery.php index 0b35c2f..34a7c34 100644 --- a/lib/Repository/Support/RepoQuery.php +++ b/lib/Repository/Support/RepoQuery.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Support; +/** Static helpers for safe SQL query building: LIKE escaping, limit/offset clamping, ID list normalization, and enum filtering. */ class RepoQuery { private static function normalizeLikeValue(string $value): string diff --git a/lib/Repository/Tenant/TenantMicrosoftAuthRepository.php b/lib/Repository/Tenant/TenantMicrosoftAuthRepository.php index ec3e446..40b24fd 100644 --- a/lib/Repository/Tenant/TenantMicrosoftAuthRepository.php +++ b/lib/Repository/Tenant/TenantMicrosoftAuthRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Tenant; use MintyPHP\DB; +/** Persists Microsoft Entra ID OAuth settings per tenant, including encrypted client secrets. */ class TenantMicrosoftAuthRepository implements TenantMicrosoftAuthRepositoryInterface { private function unwrap($row): ?array diff --git a/lib/Repository/Tenant/TenantMicrosoftAuthRepositoryInterface.php b/lib/Repository/Tenant/TenantMicrosoftAuthRepositoryInterface.php index 04f9831..eab3a38 100644 --- a/lib/Repository/Tenant/TenantMicrosoftAuthRepositoryInterface.php +++ b/lib/Repository/Tenant/TenantMicrosoftAuthRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Tenant; +/** Contract for storing and retrieving Microsoft Entra ID SSO configuration per tenant. */ interface TenantMicrosoftAuthRepositoryInterface { public function findByTenantId(int $tenantId): ?array; diff --git a/lib/Repository/Tenant/TenantRepository.php b/lib/Repository/Tenant/TenantRepository.php index 428897e..d19c369 100644 --- a/lib/Repository/Tenant/TenantRepository.php +++ b/lib/Repository/Tenant/TenantRepository.php @@ -7,6 +7,7 @@ use MintyPHP\Domain\Taxonomy\TenantStatus; use MintyPHP\Repository\Support\RepoQuery; use MintyPHP\Repository\Support\RepositoryArrayHelper; +/** Reads and writes tenant records with pagination, search, and status filtering. */ class TenantRepository implements TenantRepositoryInterface { public function list(): array diff --git a/lib/Repository/Tenant/TenantRepositoryInterface.php b/lib/Repository/Tenant/TenantRepositoryInterface.php index 58aa81d..05af20c 100644 --- a/lib/Repository/Tenant/TenantRepositoryInterface.php +++ b/lib/Repository/Tenant/TenantRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Tenant; +/** Contract for tenant CRUD and filtered listing. */ interface TenantRepositoryInterface { public function list(): array; diff --git a/lib/Repository/Tenant/UserTenantRepository.php b/lib/Repository/Tenant/UserTenantRepository.php index ef79c9a..56da46f 100644 --- a/lib/Repository/Tenant/UserTenantRepository.php +++ b/lib/Repository/Tenant/UserTenantRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\Tenant; use MintyPHP\DB; +/** Queries the many-to-many link between users and tenants. */ class UserTenantRepository implements UserTenantRepositoryInterface { public function listTenantIdsByUserId(int $userId): array diff --git a/lib/Repository/Tenant/UserTenantRepositoryInterface.php b/lib/Repository/Tenant/UserTenantRepositoryInterface.php index f2741d0..5ace2f4 100644 --- a/lib/Repository/Tenant/UserTenantRepositoryInterface.php +++ b/lib/Repository/Tenant/UserTenantRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\Tenant; +/** Contract for querying user-tenant membership and per-tenant user counts. */ interface UserTenantRepositoryInterface { public function listTenantIdsByUserId(int $userId): array; diff --git a/lib/Repository/User/UserExternalIdentityRepository.php b/lib/Repository/User/UserExternalIdentityRepository.php index ee86b06..3a20995 100644 --- a/lib/Repository/User/UserExternalIdentityRepository.php +++ b/lib/Repository/User/UserExternalIdentityRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\User; use MintyPHP\DB; +/** Links and looks up external identity provider accounts (Entra ID / OIDC) for users. */ class UserExternalIdentityRepository implements UserExternalIdentityRepositoryInterface { private function unwrap($row): ?array diff --git a/lib/Repository/User/UserExternalIdentityRepositoryInterface.php b/lib/Repository/User/UserExternalIdentityRepositoryInterface.php index 6a44cbb..cf8a991 100644 --- a/lib/Repository/User/UserExternalIdentityRepositoryInterface.php +++ b/lib/Repository/User/UserExternalIdentityRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\User; +/** Contract for linking external identity provider accounts (OIDC) to users. */ interface UserExternalIdentityRepositoryInterface { public function findByProviderTidOid(string $provider, string $tid, string $oid): ?array; diff --git a/lib/Repository/User/UserListQueryRepository.php b/lib/Repository/User/UserListQueryRepository.php index 79c4213..61c3e3f 100644 --- a/lib/Repository/User/UserListQueryRepository.php +++ b/lib/Repository/User/UserListQueryRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\User; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Builds and executes complex user list queries with search, date, role, department, and custom field filters. */ class UserListQueryRepository implements UserListQueryRepositoryInterface { private function buildUserFilters(array $options): array diff --git a/lib/Repository/User/UserListQueryRepositoryInterface.php b/lib/Repository/User/UserListQueryRepositoryInterface.php index 0f36f1b..b579732 100644 --- a/lib/Repository/User/UserListQueryRepositoryInterface.php +++ b/lib/Repository/User/UserListQueryRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\User; +/** Contract for paginated user listing with multi-criteria filtering. */ interface UserListQueryRepositoryInterface { public function listPaged(array $options): array; diff --git a/lib/Repository/User/UserReadRepository.php b/lib/Repository/User/UserReadRepository.php index c6e2abc..9b0c0f5 100644 --- a/lib/Repository/User/UserReadRepository.php +++ b/lib/Repository/User/UserReadRepository.php @@ -4,6 +4,7 @@ namespace MintyPHP\Repository\User; use MintyPHP\DB; +/** Retrieves user records and authorization snapshots; no write operations. */ class UserReadRepository implements UserReadRepositoryInterface { public function findAuthzSnapshot(int $userId): ?array diff --git a/lib/Repository/User/UserReadRepositoryInterface.php b/lib/Repository/User/UserReadRepositoryInterface.php index 52f7259..4c48a02 100644 --- a/lib/Repository/User/UserReadRepositoryInterface.php +++ b/lib/Repository/User/UserReadRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\User; +/** Contract for read-only user lookups by ID, UUID, email, and authorization snapshots. */ interface UserReadRepositoryInterface { public function findAuthzSnapshot(int $userId): ?array; diff --git a/lib/Repository/User/UserSavedFilterRepository.php b/lib/Repository/User/UserSavedFilterRepository.php index 772e372..a4e7373 100644 --- a/lib/Repository/User/UserSavedFilterRepository.php +++ b/lib/Repository/User/UserSavedFilterRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\User; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Persists and retrieves user-defined search filter presets per list context. */ class UserSavedFilterRepository implements UserSavedFilterRepositoryInterface { private function unwrapList($rows): array diff --git a/lib/Repository/User/UserSavedFilterRepositoryInterface.php b/lib/Repository/User/UserSavedFilterRepositoryInterface.php index e358c31..ec5ef12 100644 --- a/lib/Repository/User/UserSavedFilterRepositoryInterface.php +++ b/lib/Repository/User/UserSavedFilterRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\User; +/** Contract for per-user, per-context persistence of saved search filters. */ interface UserSavedFilterRepositoryInterface { public function listByUserAndContext(int $userId, string $context): array; diff --git a/lib/Repository/User/UserWriteRepository.php b/lib/Repository/User/UserWriteRepository.php index 62e1ae3..6bc4621 100644 --- a/lib/Repository/User/UserWriteRepository.php +++ b/lib/Repository/User/UserWriteRepository.php @@ -5,6 +5,7 @@ namespace MintyPHP\Repository\User; use MintyPHP\DB; use MintyPHP\Repository\Support\RepoQuery; +/** Creates and mutates user records including activation, tenant links, and preferences. */ class UserWriteRepository implements UserWriteRepositoryInterface { public function updateLastLogin(int $userId, string $provider = 'local'): void diff --git a/lib/Repository/User/UserWriteRepositoryInterface.php b/lib/Repository/User/UserWriteRepositoryInterface.php index a72885b..7a9be88 100644 --- a/lib/Repository/User/UserWriteRepositoryInterface.php +++ b/lib/Repository/User/UserWriteRepositoryInterface.php @@ -2,6 +2,7 @@ namespace MintyPHP\Repository\User; +/** Contract for user creation, updates, activation, tenant assignment, and preference changes. */ interface UserWriteRepositoryInterface { public function updateLastLogin(int $userId, string $provider = 'local'): void; diff --git a/lib/Service/Access/PermissionService.php b/lib/Service/Access/PermissionService.php index bedb56f..94b9a6e 100644 --- a/lib/Service/Access/PermissionService.php +++ b/lib/Service/Access/PermissionService.php @@ -8,6 +8,18 @@ use MintyPHP\Repository\Access\RolePermissionRepositoryInterface; use MintyPHP\Repository\Access\UserRoleRepositoryInterface; use MintyPHP\Service\Audit\SystemAuditService; +/** + * Central RBAC permission service — resolves and caches permission keys for users. + * + * Permission resolution: User → Roles (via user_roles) → Permissions (via role_permissions). + * The resolved key list is cached in two tiers: + * - API requests (stateless): in-memory array, lives for one request cycle. + * - Web requests: session store, survives across page loads until refresh. + * A forced refresh can be triggered by passing $refresh = true to getUserPermissions(). + * + * All permission key constants are defined here as the single source of truth, + * referenced by actions, services, policies, and templates. + */ class PermissionService { private array $apiPermissionCache = []; diff --git a/lib/Service/Auth/AuthService.php b/lib/Service/Auth/AuthService.php index aed1f63..94c9023 100644 --- a/lib/Service/Auth/AuthService.php +++ b/lib/Service/Auth/AuthService.php @@ -14,6 +14,21 @@ use MintyPHP\Service\User\UserTenantContextService; use MintyPHP\Session; use MintyPHP\Support\Flash; +/** + * Orchestrates authentication flows: login, registration, logout, and session refresh. + * + * Login is a cascading validation — each step must pass before the next is attempted: + * 1. Email verification status (unverified → redirect to verification) + * 2. Credential check via Auth::login (email + password) + * 3. Account active flag + * 4. Local password login allowed (at least one tenant permits it) + * 5. Permissions loaded + tenant context hydrated into session + * 6. At least one active tenant assigned + * + * Every outcome (success or failure at any step) is recorded as an audit event. + * On SSO-initiated login (loginUserById), steps 1-4 are skipped because the IdP + * already authenticated the user; steps 5-6 still apply. + */ class AuthService { public function __construct( @@ -293,6 +308,14 @@ class AuthService $this->systemAuditService->record($eventType, $outcome, $context); } + /** + * Re-validates the session against the current DB state. + * + * Uses an authz_version counter: the DB version is bumped whenever permissions or + * tenant assignments change. A mismatch with the session version triggers a full + * reload of user data, permissions, and tenant context. This is the mechanism that + * makes permission changes take effect without requiring re-login. + */ public function refreshSessionAuthState(int $userId): array { if ($userId <= 0) { diff --git a/lib/Service/Import/ImportService.php b/lib/Service/Import/ImportService.php index c8a931a..508067e 100644 --- a/lib/Service/Import/ImportService.php +++ b/lib/Service/Import/ImportService.php @@ -9,6 +9,23 @@ use MintyPHP\Service\Import\Profile\ImportProfileInterface; use MintyPHP\Service\Settings\SettingsDefaultsGateway; use MintyPHP\Service\Tenant\TenantScopeService; +/** + * Orchestrates CSV imports through a three-phase workflow: + * + * 1. analyzeUpload() — validates file, detects delimiter/headers, stores temp file, + * returns a session-scoped token for subsequent calls. + * 2. preview() — maps columns → target fields, validates each row via the + * profile's dryRunRow(), returns would-create/skip/fail counts. + * 3. commit() — identical mapping + validation, then commitRow() for real + * writes; wrapped in an audit run (start → finish). + * + * Import behavior is pluggable via ImportProfileInterface. Each profile defines + * allowed/required target fields, validation rules, and the actual create logic. + * The PROFILE_PERMISSION_MAP links profile keys to the required permission. + * + * Cross-user token reuse is prevented by verifying user_id in preview/commit. + * Assignment columns (tenant/role/department) require a separate permission check. + */ class ImportService { private const MAX_ROWS = 20000; diff --git a/lib/Service/Tenant/TenantScopeService.php b/lib/Service/Tenant/TenantScopeService.php index c8bcfc8..6563a67 100644 --- a/lib/Service/Tenant/TenantScopeService.php +++ b/lib/Service/Tenant/TenantScopeService.php @@ -7,6 +7,19 @@ use MintyPHP\Repository\Tenant\TenantRepositoryInterface; use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface; use MintyPHP\Service\Access\PermissionService; +/** + * Enforces tenant-boundary access control across the application. + * + * Core rules: + * - Users with TENANT_SCOPE_GLOBAL permission bypass all tenant checks. + * - canAccess() resolves the resource's tenant IDs, intersects with the user's + * tenant IDs, and grants access only on overlap. + * - In strict mode (default): unscoped resources (no tenant assigned) are denied. + * - In permissive mode: unscoped resources are allowed. + * - Resources whose tenants all became inactive are always denied (not treated as unscoped). + * - mergeTenantIdsPreservingOutOfScope() ensures that a scoped admin cannot + * accidentally remove tenant assignments outside their own scope. + */ class TenantScopeService { public function __construct( diff --git a/lib/Service/User/UserAccountService.php b/lib/Service/User/UserAccountService.php index 289a35c..667499f 100644 --- a/lib/Service/User/UserAccountService.php +++ b/lib/Service/User/UserAccountService.php @@ -10,6 +10,20 @@ use MintyPHP\Repository\User\UserWriteRepositoryInterface; use MintyPHP\Service\Audit\SystemAuditService; use MintyPHP\Service\Tenant\TenantScopeService; +/** + * User account lifecycle: creation, updates, activation, deletion, and self-service profile edits. + * + * Write operations (createFromAdmin, register) run inside a DB transaction to ensure + * atomicity of user record + tenant/role/department assignments. On any failure the + * transaction is rolled back and no partial state is left behind. + * + * Tenant-scoped operations (delete, bulk activate/deactivate) filter UUIDs through + * TenantScopeService before executing, so a scoped admin can only affect users + * within their own tenant boundary. Self-delete and self-deactivate are always blocked. + * + * Every state change is recorded via SystemAuditService with before/after snapshots + * where applicable (e.g. active flag, locale, theme, primary tenant). + */ class UserAccountService { public function __construct(