add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
<?php
|
|
|
|
|
|
refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.
Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers
Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n
Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling
All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:12:49 +01:00
|
|
|
namespace MintyPHP\Module\Audit\Repository;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
|
2026-03-05 11:17:42 +01:00
|
|
|
use MintyPHP\DB;
|
refactor(audit): extract audit domain into self-contained module
Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.
Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers
Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n
Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling
All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:12:49 +01:00
|
|
|
use MintyPHP\Module\Audit\Domain\UserLifecycleAction;
|
|
|
|
|
use MintyPHP\Module\Audit\Domain\UserLifecycleStatus;
|
|
|
|
|
use MintyPHP\Module\Audit\Domain\UserLifecycleTriggerType;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
use MintyPHP\Repository\Support\RepoQuery;
|
|
|
|
|
|
2026-03-13 21:58:51 +01:00
|
|
|
/** Records user lifecycle transitions (deactivation, deletion, restore) with reason codes and snapshots. */
|
2026-03-26 12:33:59 +01:00
|
|
|
class UserLifecycleAuditRepository
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
2026-03-04 15:56:58 +01:00
|
|
|
private const FILTER_OPTIONS_LIMIT_MAX = 200;
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function create(array $row): int|false
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
$id = DB::insert(
|
|
|
|
|
'insert into user_lifecycle_audit_log (
|
|
|
|
|
run_uuid, action, trigger_type, status, reason_code,
|
|
|
|
|
policy_deactivate_days, policy_delete_days,
|
|
|
|
|
actor_user_id, target_user_id, target_user_uuid, target_user_email,
|
|
|
|
|
snapshot_enc, snapshot_version, created_at
|
|
|
|
|
) values (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())',
|
|
|
|
|
(string) ($row['run_uuid'] ?? ''),
|
|
|
|
|
(string) ($row['action'] ?? ''),
|
|
|
|
|
(string) ($row['trigger_type'] ?? ''),
|
|
|
|
|
(string) ($row['status'] ?? ''),
|
|
|
|
|
$row['reason_code'] ?? null,
|
|
|
|
|
(string) ((int) ($row['policy_deactivate_days'] ?? 0)),
|
|
|
|
|
(string) ((int) ($row['policy_delete_days'] ?? 0)),
|
|
|
|
|
$row['actor_user_id'] !== null ? (string) ((int) $row['actor_user_id']) : null,
|
|
|
|
|
$row['target_user_id'] !== null ? (string) ((int) $row['target_user_id']) : null,
|
|
|
|
|
$row['target_user_uuid'] ?? null,
|
|
|
|
|
$row['target_user_email'] ?? null,
|
|
|
|
|
$row['snapshot_enc'] ?? null,
|
|
|
|
|
(string) ((int) ($row['snapshot_version'] ?? 1))
|
|
|
|
|
);
|
|
|
|
|
return $id ? (int) $id : false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($id <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
$normalizedStatus = UserLifecycleStatus::tryNormalize($status);
|
|
|
|
|
if ($normalizedStatus === null) {
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$updated = DB::update(
|
|
|
|
|
'update user_lifecycle_audit_log set status = ?, reason_code = ? where id = ?',
|
2026-03-04 15:56:58 +01:00
|
|
|
$normalizedStatus->value,
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
$reasonCode,
|
|
|
|
|
(string) $id
|
|
|
|
|
);
|
|
|
|
|
return $updated !== false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function listPaged(array $filters): array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
$search = trim((string) ($filters['search'] ?? ''));
|
2026-03-04 15:56:58 +01:00
|
|
|
$actions = RepoQuery::normalizeStringList(
|
|
|
|
|
$filters['actions'] ?? '',
|
|
|
|
|
self::FILTER_OPTIONS_LIMIT_MAX,
|
2026-04-14 12:54:20 +02:00
|
|
|
static fn (string $value): string => UserLifecycleAction::tryNormalize($value)->value ?? ''
|
2026-03-04 15:56:58 +01:00
|
|
|
);
|
|
|
|
|
$statuses = RepoQuery::normalizeStringList(
|
|
|
|
|
$filters['statuses'] ?? '',
|
|
|
|
|
self::FILTER_OPTIONS_LIMIT_MAX,
|
2026-04-14 12:54:20 +02:00
|
|
|
static fn (string $value): string => UserLifecycleStatus::tryNormalize($value)->value ?? ''
|
2026-03-04 15:56:58 +01:00
|
|
|
);
|
|
|
|
|
$triggerTypes = RepoQuery::normalizeStringList(
|
|
|
|
|
$filters['trigger_types'] ?? '',
|
|
|
|
|
self::FILTER_OPTIONS_LIMIT_MAX,
|
2026-04-14 12:54:20 +02:00
|
|
|
static fn (string $value): string => UserLifecycleTriggerType::tryNormalize($value)->value ?? ''
|
2026-03-04 15:56:58 +01:00
|
|
|
);
|
|
|
|
|
$actorUserIds = array_slice(
|
|
|
|
|
RepoQuery::normalizeIdList($filters['actor_user_ids'] ?? ''),
|
|
|
|
|
0,
|
|
|
|
|
self::FILTER_OPTIONS_LIMIT_MAX
|
|
|
|
|
);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
|
|
|
|
|
$createdTo = trim((string) ($filters['created_to'] ?? ''));
|
|
|
|
|
|
|
|
|
|
[$limit, $offset] = RepoQuery::sanitizeLimitOffset($filters, 20, 1, 200, 0);
|
|
|
|
|
[$order, $dir] = RepoQuery::sanitizeOrder(
|
|
|
|
|
$filters,
|
|
|
|
|
['id', 'created_at', 'action', 'trigger_type', 'status'],
|
|
|
|
|
'created_at',
|
|
|
|
|
'desc'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$where = [];
|
|
|
|
|
$params = [];
|
|
|
|
|
RepoQuery::addLikeFilter(
|
|
|
|
|
$where,
|
|
|
|
|
$params,
|
|
|
|
|
[
|
|
|
|
|
'user_lifecycle_audit_log.run_uuid',
|
|
|
|
|
'user_lifecycle_audit_log.target_user_uuid',
|
|
|
|
|
'user_lifecycle_audit_log.target_user_email',
|
|
|
|
|
'user_lifecycle_audit_log.reason_code',
|
|
|
|
|
],
|
|
|
|
|
$search
|
|
|
|
|
);
|
2026-03-04 15:56:58 +01:00
|
|
|
if ($actions !== []) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.action in (???)';
|
|
|
|
|
$params[] = $actions;
|
|
|
|
|
}
|
|
|
|
|
if ($statuses !== []) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.status in (???)';
|
|
|
|
|
$params[] = $statuses;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
if ($triggerTypes !== []) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.trigger_type in (???)';
|
|
|
|
|
$params[] = $triggerTypes;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
if ($actorUserIds !== []) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.actor_user_id in (???)';
|
|
|
|
|
$params[] = $actorUserIds;
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdFrom)) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.created_at >= ?';
|
|
|
|
|
$params[] = $createdFrom . ' 00:00:00';
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdTo)) {
|
|
|
|
|
$where[] = 'user_lifecycle_audit_log.created_at <= ?';
|
|
|
|
|
$params[] = $createdTo . ' 23:59:59';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
|
|
|
|
|
$fromSql = ' from user_lifecycle_audit_log ' .
|
|
|
|
|
'left join users actor_user on actor_user.id = user_lifecycle_audit_log.actor_user_id ' .
|
|
|
|
|
'left join users restored_by_user on restored_by_user.id = user_lifecycle_audit_log.restored_by_user_id ' .
|
|
|
|
|
'left join users restored_user on restored_user.id = user_lifecycle_audit_log.restored_user_id ';
|
|
|
|
|
|
|
|
|
|
$total = (int) (DB::selectValue('select count(*)' . $fromSql . $whereSql, ...$params) ?? 0);
|
|
|
|
|
|
|
|
|
|
$rows = DB::select(
|
|
|
|
|
'select
|
|
|
|
|
user_lifecycle_audit_log.id,
|
|
|
|
|
user_lifecycle_audit_log.run_uuid,
|
|
|
|
|
user_lifecycle_audit_log.action,
|
|
|
|
|
user_lifecycle_audit_log.trigger_type,
|
|
|
|
|
user_lifecycle_audit_log.status,
|
|
|
|
|
user_lifecycle_audit_log.reason_code,
|
|
|
|
|
user_lifecycle_audit_log.policy_deactivate_days,
|
|
|
|
|
user_lifecycle_audit_log.policy_delete_days,
|
|
|
|
|
user_lifecycle_audit_log.actor_user_id,
|
|
|
|
|
user_lifecycle_audit_log.target_user_id,
|
|
|
|
|
user_lifecycle_audit_log.target_user_uuid,
|
|
|
|
|
user_lifecycle_audit_log.target_user_email,
|
|
|
|
|
user_lifecycle_audit_log.snapshot_version,
|
|
|
|
|
user_lifecycle_audit_log.restored_at,
|
|
|
|
|
user_lifecycle_audit_log.restored_by_user_id,
|
|
|
|
|
user_lifecycle_audit_log.restored_user_id,
|
|
|
|
|
user_lifecycle_audit_log.created_at,
|
|
|
|
|
actor_user.uuid,
|
|
|
|
|
actor_user.display_name,
|
|
|
|
|
actor_user.email,
|
|
|
|
|
restored_by_user.uuid,
|
|
|
|
|
restored_by_user.display_name,
|
|
|
|
|
restored_by_user.email,
|
|
|
|
|
restored_user.uuid,
|
|
|
|
|
restored_user.display_name,
|
|
|
|
|
restored_user.email
|
|
|
|
|
' . $fromSql . $whereSql .
|
|
|
|
|
sprintf(' order by user_lifecycle_audit_log.`%s` %s limit ? offset ?', $order, $dir),
|
|
|
|
|
...array_merge($params, [(string) $limit, (string) $offset])
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$normalized = [];
|
|
|
|
|
if (is_array($rows)) {
|
|
|
|
|
foreach ($rows as $row) {
|
2026-02-23 12:58:19 +01:00
|
|
|
$item = $this->normalizeRow($row, false);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
if ($item !== null) {
|
|
|
|
|
$normalized[] = $item;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ['total' => $total, 'rows' => $normalized];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function find(int $id): ?array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($id <= 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row = DB::selectOne(
|
|
|
|
|
'select
|
|
|
|
|
user_lifecycle_audit_log.id,
|
|
|
|
|
user_lifecycle_audit_log.run_uuid,
|
|
|
|
|
user_lifecycle_audit_log.action,
|
|
|
|
|
user_lifecycle_audit_log.trigger_type,
|
|
|
|
|
user_lifecycle_audit_log.status,
|
|
|
|
|
user_lifecycle_audit_log.reason_code,
|
|
|
|
|
user_lifecycle_audit_log.policy_deactivate_days,
|
|
|
|
|
user_lifecycle_audit_log.policy_delete_days,
|
|
|
|
|
user_lifecycle_audit_log.actor_user_id,
|
|
|
|
|
user_lifecycle_audit_log.target_user_id,
|
|
|
|
|
user_lifecycle_audit_log.target_user_uuid,
|
|
|
|
|
user_lifecycle_audit_log.target_user_email,
|
|
|
|
|
user_lifecycle_audit_log.snapshot_enc,
|
|
|
|
|
user_lifecycle_audit_log.snapshot_version,
|
|
|
|
|
user_lifecycle_audit_log.restored_at,
|
|
|
|
|
user_lifecycle_audit_log.restored_by_user_id,
|
|
|
|
|
user_lifecycle_audit_log.restored_user_id,
|
|
|
|
|
user_lifecycle_audit_log.created_at,
|
|
|
|
|
actor_user.uuid,
|
|
|
|
|
actor_user.display_name,
|
|
|
|
|
actor_user.email,
|
|
|
|
|
restored_by_user.uuid,
|
|
|
|
|
restored_by_user.display_name,
|
|
|
|
|
restored_by_user.email,
|
|
|
|
|
restored_user.uuid,
|
|
|
|
|
restored_user.display_name,
|
|
|
|
|
restored_user.email
|
|
|
|
|
from user_lifecycle_audit_log
|
|
|
|
|
left join users actor_user on actor_user.id = user_lifecycle_audit_log.actor_user_id
|
|
|
|
|
left join users restored_by_user on restored_by_user.id = user_lifecycle_audit_log.restored_by_user_id
|
|
|
|
|
left join users restored_user on restored_user.id = user_lifecycle_audit_log.restored_user_id
|
|
|
|
|
where user_lifecycle_audit_log.id = ?
|
|
|
|
|
limit 1',
|
|
|
|
|
(string) $id
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
return $this->normalizeRow($row, true);
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($id <= 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$query = 'select
|
|
|
|
|
id, run_uuid, action, trigger_type, status, reason_code,
|
|
|
|
|
policy_deactivate_days, policy_delete_days, actor_user_id,
|
|
|
|
|
target_user_id, target_user_uuid, target_user_email,
|
|
|
|
|
snapshot_enc, snapshot_version, restored_at,
|
|
|
|
|
restored_by_user_id, restored_user_id, created_at
|
|
|
|
|
from user_lifecycle_audit_log
|
|
|
|
|
where id = ?
|
|
|
|
|
and action = \'delete\'
|
|
|
|
|
and status = \'success\'
|
|
|
|
|
limit 1';
|
|
|
|
|
if ($forUpdate) {
|
|
|
|
|
$query .= ' for update';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$row = DB::selectOne($query, (string) $id);
|
|
|
|
|
if (!is_array($row)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$item = $row['user_lifecycle_audit_log'] ?? $row;
|
|
|
|
|
return is_array($item) ? $item : null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function markRestored(int $id, int $restoredBy, int $restoredUserId): bool
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($id <= 0 || $restoredBy <= 0 || $restoredUserId <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$updated = DB::update(
|
|
|
|
|
'update user_lifecycle_audit_log
|
|
|
|
|
set restored_at = NOW(),
|
|
|
|
|
restored_by_user_id = ?,
|
|
|
|
|
restored_user_id = ?
|
|
|
|
|
where id = ? and restored_at is null',
|
|
|
|
|
(string) $restoredBy,
|
|
|
|
|
(string) $restoredUserId,
|
|
|
|
|
(string) $id
|
|
|
|
|
);
|
|
|
|
|
return (int) $updated > 0;
|
|
|
|
|
}
|
|
|
|
|
|
feat(user-lifecycle): cockpit foundation — KPI row + aside quick actions
Phase 1 of the Stripe-style policy-cockpit redesign for the user-lifecycle
settings page. Pure server-rendering — no async, no JS components, no
sparklines yet (those land in later phases).
Adds a four-tile KPI row above the configuration form (Last run,
Deactivated/30d, Deleted/30d, Pending deletion/7d), populates the
previously empty aside with three quick actions (Run policy now,
Purge logs, Policy reference link), and surfaces a relative-time +
status hint under the existing Run-Now collapsible.
Module-isolation is preserved through a new read-side contract:
* core/Service/Audit/UserLifecycleAuditDashboardInterface — read-only
pendant to the existing write-side UserLifecycleAuditInterface.
Methods: lastRun(), summaryByAction(int days), countActionInWindow(...).
* core/Service/Audit/NullUserLifecycleAuditDashboard — fail-closed
default when the audit module is disabled. KPI tiles 1-3 then
render "—"; tile 4 (pending deletion) keeps working because it
lives in the core domain.
* modules/audit/.../Service/UserLifecycleAuditDashboardService — the
module's implementation; reads through the existing
UserLifecycleAuditRepository (extended with three new aggregation
queries: lastRun, countByActionStatusSinceTimestamp, countSinceTimestamp).
* AuditContainerRegistrar binds the interface to the module impl;
registerContainer.php registers the Null fallback before module
bindings, mirroring how the write-side audit interface is wired.
The new core service UserLifecyclePolicyDashboardService computes
the pending-deletion-window count from the users table directly
(no audit dependency) — defensive when both policy days are 0
(returns 0 rather than running an unbounded query).
New shared template partial templates/partials/app-kpi-row.phtml is
generic — accepts a $kpiTiles array of {label, count, icon, iconTone,
href, tooltip} and reuses the existing app-tile primitive. Other
settings pages can pick it up without ceremony.
Includes:
* PHPUnit tests for both new services (happy path + Null-fallback +
policy-disabled edge cases).
* AuditModuleIsolationContractTest allowlist extended for the new
interface and module service.
* 14 new translation keys in both default_de.json and default_en.json
(i18n parity verified).
All six quality gates green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:36:28 +02:00
|
|
|
/**
|
|
|
|
|
* Count audit-log events for a single action+status within the last $days days (UTC).
|
|
|
|
|
*/
|
|
|
|
|
public function countActionInWindow(string $action, int $days, string $status): int
|
|
|
|
|
{
|
|
|
|
|
if ($days <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
$value = DB::selectValue(
|
|
|
|
|
'select count(*)
|
|
|
|
|
from user_lifecycle_audit_log
|
|
|
|
|
where action = ?
|
|
|
|
|
and status = ?
|
|
|
|
|
and created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ? DAY)',
|
|
|
|
|
$action,
|
|
|
|
|
$status,
|
|
|
|
|
(string) $days
|
|
|
|
|
);
|
|
|
|
|
return (int) ($value ?? 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Map of action → count for the given status within the last $days days (UTC).
|
|
|
|
|
*
|
|
|
|
|
* @return array<string, int>
|
|
|
|
|
*/
|
|
|
|
|
public function sumByActionInWindow(int $days, string $status): array
|
|
|
|
|
{
|
|
|
|
|
if ($days <= 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
$rows = DB::select(
|
|
|
|
|
'select action, count(*) as cnt
|
|
|
|
|
from user_lifecycle_audit_log
|
|
|
|
|
where status = ?
|
|
|
|
|
and created_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ? DAY)
|
|
|
|
|
group by action',
|
|
|
|
|
$status,
|
|
|
|
|
(string) $days
|
|
|
|
|
);
|
|
|
|
|
$summary = [];
|
|
|
|
|
if (is_array($rows)) {
|
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
|
$item = is_array($row) ? ($row['user_lifecycle_audit_log'] ?? $row) : null;
|
|
|
|
|
if (!is_array($item)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$action = trim((string) ($item['action'] ?? ''));
|
|
|
|
|
if ($action === '') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$summary[$action] = (int) ($item['cnt'] ?? 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $summary;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
public function purgeOlderThanDays(int $days): int
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if ($days <= 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$cutoff = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))
|
|
|
|
|
->modify('-' . $days . ' days')
|
|
|
|
|
->format('Y-m-d H:i:s');
|
|
|
|
|
$deleted = DB::delete('delete from user_lifecycle_audit_log where created_at < ?', $cutoff);
|
|
|
|
|
return is_int($deleted) ? $deleted : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 12:58:19 +01:00
|
|
|
private function normalizeRow(mixed $row, bool $includeSnapshot): ?array
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
{
|
|
|
|
|
if (!is_array($row)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$item = $row['user_lifecycle_audit_log'] ?? [];
|
|
|
|
|
if (!is_array($item) || !isset($item['id'])) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$actor = is_array($row['actor_user'] ?? null) ? $row['actor_user'] : [];
|
|
|
|
|
$restoredBy = is_array($row['restored_by_user'] ?? null) ? $row['restored_by_user'] : [];
|
|
|
|
|
$restoredUser = is_array($row['restored_user'] ?? null) ? $row['restored_user'] : [];
|
|
|
|
|
|
|
|
|
|
$item['actor_user_uuid'] = (string) ($actor['uuid'] ?? '');
|
|
|
|
|
$item['actor_user_display_name'] = (string) ($actor['display_name'] ?? '');
|
|
|
|
|
$item['actor_user_email'] = (string) ($actor['email'] ?? '');
|
|
|
|
|
$item['restored_by_user_uuid'] = (string) ($restoredBy['uuid'] ?? '');
|
|
|
|
|
$item['restored_by_user_display_name'] = (string) ($restoredBy['display_name'] ?? '');
|
|
|
|
|
$item['restored_by_user_email'] = (string) ($restoredBy['email'] ?? '');
|
|
|
|
|
$item['restored_user_uuid'] = (string) ($restoredUser['uuid'] ?? '');
|
|
|
|
|
$item['restored_user_display_name'] = (string) ($restoredUser['display_name'] ?? '');
|
|
|
|
|
$item['restored_user_email'] = (string) ($restoredUser['email'] ?? '');
|
|
|
|
|
|
|
|
|
|
if (!$includeSnapshot) {
|
|
|
|
|
unset($item['snapshot_enc']);
|
|
|
|
|
}
|
|
|
|
|
return $item;
|
|
|
|
|
}
|
2026-03-04 15:56:58 +01:00
|
|
|
|
add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00
|
|
|
}
|