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>
This commit is contained in:
2026-03-25 21:12:49 +01:00
parent 12a837bda9
commit 0c351f6aff
176 changed files with 2157 additions and 834 deletions

View File

@@ -1,338 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
use MintyPHP\DB;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
/** Records API request/response audit entries with status codes, timing, and error details. */
class ApiAuditLogRepository implements ApiAuditLogRepositoryInterface
{
private const FILTER_OPTIONS_LIMIT_MAX = 200;
public function create(array $data): int|false
{
$id = DB::insert(
'insert into api_audit_log (
request_id, method, path, query_json, status_code, duration_ms, error_code,
user_id, tenant_id, api_token_id, token_tenant_id, ip, user_agent, created_at
) values (?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())',
(string) ($data['request_id'] ?? ''),
(string) ($data['method'] ?? ''),
(string) ($data['path'] ?? ''),
$data['query_json'] ?? null,
(string) ((int) ($data['status_code'] ?? 0)),
$data['duration_ms'] !== null ? (string) ((int) $data['duration_ms']) : null,
$data['error_code'] ?? null,
$data['user_id'] !== null ? (string) ((int) $data['user_id']) : null,
$data['tenant_id'] !== null ? (string) ((int) $data['tenant_id']) : null,
$data['api_token_id'] !== null ? (string) ((int) $data['api_token_id']) : null,
$data['token_tenant_id'] !== null ? (string) ((int) $data['token_tenant_id']) : null,
$data['ip'] ?? null,
$data['user_agent'] ?? null
);
return $id ? (int) $id : false;
}
public function listPaged(array $filters): array
{
$search = trim((string) ($filters['search'] ?? ''));
$status = strtolower(trim((string) ($filters['status'] ?? '')));
$method = strtoupper(trim((string) ($filters['method'] ?? '')));
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
$createdTo = trim((string) ($filters['created_to'] ?? ''));
$tenantIds = array_slice(
RepoQuery::normalizeIdList($filters['tenant_ids'] ?? ''),
0,
self::FILTER_OPTIONS_LIMIT_MAX
);
$userIds = array_slice(
RepoQuery::normalizeIdList($filters['user_ids'] ?? ''),
0,
self::FILTER_OPTIONS_LIMIT_MAX
);
[$limit, $offset] = RepoQuery::sanitizeLimitOffset($filters, 20, 1, 200, 0);
[$order, $dir] = RepoQuery::sanitizeOrder(
$filters,
['id', 'created_at', 'status_code', 'duration_ms', 'method', 'path'],
'created_at',
'desc'
);
$where = [];
$params = [];
RepoQuery::addLikeFilter(
$where,
$params,
['api_audit_log.path', 'api_audit_log.request_id', 'api_audit_log.ip', 'api_audit_log.error_code'],
$search
);
if (in_array($status, ['2xx', '4xx', '5xx'], true)) {
$rangeStart = (int) $status[0] * 100;
$where[] = 'api_audit_log.status_code between ? and ?';
$params[] = (string) $rangeStart;
$params[] = (string) ($rangeStart + 99);
} elseif ($status !== '' && ctype_digit($status)) {
$statusCode = (int) $status;
if ($statusCode >= 100 && $statusCode <= 599) {
$where[] = 'api_audit_log.status_code = ?';
$params[] = (string) $statusCode;
}
}
if (in_array($method, ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], true)) {
$where[] = 'api_audit_log.method = ?';
$params[] = $method;
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdFrom)) {
$where[] = 'api_audit_log.created_at >= ?';
$params[] = $createdFrom . ' 00:00:00';
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdTo)) {
$where[] = 'api_audit_log.created_at <= ?';
$params[] = $createdTo . ' 23:59:59';
}
if ($tenantIds !== []) {
$where[] = 'api_audit_log.tenant_id in (???)';
$params[] = $tenantIds;
}
if ($userIds !== []) {
$where[] = 'api_audit_log.user_id in (???)';
$params[] = $userIds;
}
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
$fromSql = ' from api_audit_log ' .
'left join users on users.id = api_audit_log.user_id ' .
'left join tenants on tenants.id = api_audit_log.tenant_id ';
$total = (int) (DB::selectValue('select count(*)' . $fromSql . $whereSql, ...$params) ?? 0);
$rows = DB::select(
'select
api_audit_log.id,
api_audit_log.request_id,
api_audit_log.method,
api_audit_log.path,
api_audit_log.query_json,
api_audit_log.status_code,
api_audit_log.duration_ms,
api_audit_log.error_code,
api_audit_log.user_id,
api_audit_log.tenant_id,
api_audit_log.api_token_id,
api_audit_log.token_tenant_id,
api_audit_log.ip,
api_audit_log.user_agent,
api_audit_log.created_at,
users.id,
users.uuid,
users.display_name,
users.email,
tenants.id,
tenants.uuid,
tenants.description
' . $fromSql . $whereSql .
sprintf(' order by api_audit_log.`%s` %s limit ? offset ?', $order, $dir),
...array_merge($params, [(string) $limit, (string) $offset])
);
$normalized = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$item = $this->normalizeRow($row);
if ($item !== null) {
$normalized[] = $item;
}
}
}
return [
'total' => $total,
'rows' => $normalized,
];
}
public function listFilterOptions(int $limit = self::FILTER_OPTIONS_LIMIT_MAX): array
{
$limit = max(1, min(self::FILTER_OPTIONS_LIMIT_MAX, $limit));
$methodRows = DB::select(
'select
api_audit_log.method,
max(api_audit_log.created_at) as last_used_at
from api_audit_log
where api_audit_log.method is not null
and api_audit_log.method <> \'\'
group by api_audit_log.method
order by last_used_at desc
limit ?',
(string) $limit
);
$methods = [];
if (is_array($methodRows)) {
foreach ($methodRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$method = strtoupper(trim((string) ($flat['method'] ?? '')));
if ($method === '') {
continue;
}
$methods[] = $method;
}
}
$methods = array_values(array_unique($methods));
$userRows = DB::select(
'select
api_audit_log.user_id,
max(api_audit_log.created_at) as last_used_at,
max(users.id) as user_exists_id,
max(users.display_name) as user_display_name,
max(users.email) as user_email
from api_audit_log
left join users on users.id = api_audit_log.user_id
where api_audit_log.user_id is not null
and api_audit_log.user_id > 0
group by api_audit_log.user_id
order by last_used_at desc
limit ?',
(string) $limit
);
$users = [];
if (is_array($userRows)) {
foreach ($userRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$id = (int) ($flat['user_id'] ?? 0);
if ($id <= 0) {
continue;
}
$users[] = [
'id' => $id,
'display_name' => trim((string) ($flat['user_display_name'] ?? '')),
'email' => trim((string) ($flat['user_email'] ?? '')),
'exists' => (int) ($flat['user_exists_id'] ?? 0) > 0,
];
}
}
$tenantRows = DB::select(
'select
api_audit_log.tenant_id,
max(api_audit_log.created_at) as last_used_at,
max(tenants.id) as tenant_exists_id,
max(tenants.description) as tenant_description
from api_audit_log
left join tenants on tenants.id = api_audit_log.tenant_id
where api_audit_log.tenant_id is not null
and api_audit_log.tenant_id > 0
group by api_audit_log.tenant_id
order by last_used_at desc
limit ?',
(string) $limit
);
$tenants = [];
if (is_array($tenantRows)) {
foreach ($tenantRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$id = (int) ($flat['tenant_id'] ?? 0);
if ($id <= 0) {
continue;
}
$tenants[] = [
'id' => $id,
'description' => trim((string) ($flat['tenant_description'] ?? '')),
'exists' => (int) ($flat['tenant_exists_id'] ?? 0) > 0,
];
}
}
return [
'methods' => $methods,
'users' => $users,
'tenants' => $tenants,
];
}
public function find(int $id): ?array
{
$row = DB::selectOne(
'select
api_audit_log.id,
api_audit_log.request_id,
api_audit_log.method,
api_audit_log.path,
api_audit_log.query_json,
api_audit_log.status_code,
api_audit_log.duration_ms,
api_audit_log.error_code,
api_audit_log.user_id,
api_audit_log.tenant_id,
api_audit_log.api_token_id,
api_audit_log.token_tenant_id,
api_audit_log.ip,
api_audit_log.user_agent,
api_audit_log.created_at,
users.id,
users.uuid,
users.display_name,
users.email,
tenants.id,
tenants.uuid,
tenants.description
from api_audit_log
left join users on users.id = api_audit_log.user_id
left join tenants on tenants.id = api_audit_log.tenant_id
where api_audit_log.id = ?
limit 1',
(string) $id
);
return $this->normalizeRow($row);
}
public function purgeOlderThanDays(int $days): int
{
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 api_audit_log where created_at < ?', $cutoff);
return is_int($deleted) ? $deleted : 0;
}
private function normalizeRow(mixed $row): ?array
{
if (!is_array($row)) {
return null;
}
$log = $row['api_audit_log'] ?? [];
if (!is_array($log) || !isset($log['id'])) {
return null;
}
$user = is_array($row['users'] ?? null) ? $row['users'] : [];
$tenant = is_array($row['tenants'] ?? null) ? $row['tenants'] : [];
$log['user_uuid'] = (string) ($user['uuid'] ?? '');
$log['user_display_name'] = (string) ($user['display_name'] ?? '');
$log['user_email'] = (string) ($user['email'] ?? '');
$log['tenant_uuid'] = (string) ($tenant['uuid'] ?? '');
$log['tenant_description'] = (string) ($tenant['description'] ?? '');
return $log;
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
/** Contract for API request audit log creation, pagination, and retention purge. */
interface ApiAuditLogRepositoryInterface
{
public function create(array $data): int|false;
public function listPaged(array $filters): array;
public function listFilterOptions(int $limit = 200): array;
public function find(int $id): ?array;
public function purgeOlderThanDays(int $days): int;
}

View File

@@ -1,302 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
use MintyPHP\DB;
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
/** Tracks CSV import runs including source file, row counts, status, and completion metadata. */
class ImportAuditRunRepository implements ImportAuditRunRepositoryInterface
{
private const FILTER_OPTIONS_LIMIT_MAX = 200;
public function createRunning(array $data): int|false
{
$id = DB::insert(
'insert into import_audit_runs (
run_uuid, profile_key, status, source_filename, mapped_targets_csv, started_at, user_id, current_tenant_id
) values (?,?,?,?,?,NOW(),?,?)',
(string) ($data['run_uuid'] ?? ''),
(string) ($data['profile_key'] ?? ''),
(string) ($data['status'] ?? ImportAuditStatus::Running->value),
$data['source_filename'] ?? null,
$data['mapped_targets_csv'] ?? null,
$data['user_id'] !== null ? (string) ((int) $data['user_id']) : null,
$data['current_tenant_id'] !== null ? (string) ((int) $data['current_tenant_id']) : null
);
return $id ? (int) $id : false;
}
public function finishById(int $id, array $data): bool
{
if ($id <= 0) {
return false;
}
$updated = DB::update(
'update import_audit_runs
set status = ?,
rows_total = ?,
created_count = ?,
skipped_count = ?,
failed_count = ?,
error_codes_json = ?,
duration_ms = ?,
finished_at = NOW()
where id = ?',
(string) ($data['status'] ?? ImportAuditStatus::Failed->value),
(string) ((int) ($data['rows_total'] ?? 0)),
(string) ((int) ($data['created_count'] ?? 0)),
(string) ((int) ($data['skipped_count'] ?? 0)),
(string) ((int) ($data['failed_count'] ?? 0)),
$data['error_codes_json'] ?? null,
$data['duration_ms'] !== null ? (string) ((int) $data['duration_ms']) : null,
(string) $id
);
return (int) $updated > 0;
}
public function listPaged(array $filters): array
{
$search = trim((string) ($filters['search'] ?? ''));
$profileKey = strtolower(trim((string) ($filters['profile_key'] ?? '')));
$status = strtolower(trim((string) ($filters['status'] ?? '')));
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
$createdTo = trim((string) ($filters['created_to'] ?? ''));
$userIds = array_slice(
RepoQuery::normalizeIdList($filters['user_ids'] ?? ''),
0,
self::FILTER_OPTIONS_LIMIT_MAX
);
[$limit, $offset] = RepoQuery::sanitizeLimitOffset($filters, 20, 1, 200, 0);
[$order, $dir] = RepoQuery::sanitizeOrder(
$filters,
[
'id',
'started_at',
'finished_at',
'duration_ms',
'rows_total',
'created_count',
'skipped_count',
'failed_count',
'status',
'profile_key',
],
'started_at',
'desc'
);
$where = [];
$params = [];
RepoQuery::addLikeFilter(
$where,
$params,
[
'import_audit_runs.run_uuid',
'import_audit_runs.source_filename',
'import_audit_runs.error_codes_json',
],
$search
);
if (in_array($profileKey, ['users', 'departments'], true)) {
$where[] = 'import_audit_runs.profile_key = ?';
$params[] = $profileKey;
}
if (ImportAuditStatus::tryNormalize($status) !== null) {
$where[] = 'import_audit_runs.status = ?';
$params[] = $status;
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdFrom)) {
$where[] = 'import_audit_runs.started_at >= ?';
$params[] = $createdFrom . ' 00:00:00';
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdTo)) {
$where[] = 'import_audit_runs.started_at <= ?';
$params[] = $createdTo . ' 23:59:59';
}
if ($userIds !== []) {
$where[] = 'import_audit_runs.user_id in (???)';
$params[] = $userIds;
}
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
$fromSql = ' from import_audit_runs ' .
'left join users on users.id = import_audit_runs.user_id ' .
'left join tenants on tenants.id = import_audit_runs.current_tenant_id ';
$total = (int) (DB::selectValue('select count(*)' . $fromSql . $whereSql, ...$params) ?? 0);
$rows = DB::select(
'select
import_audit_runs.id,
import_audit_runs.run_uuid,
import_audit_runs.profile_key,
import_audit_runs.status,
import_audit_runs.source_filename,
import_audit_runs.mapped_targets_csv,
import_audit_runs.rows_total,
import_audit_runs.created_count,
import_audit_runs.skipped_count,
import_audit_runs.failed_count,
import_audit_runs.error_codes_json,
import_audit_runs.started_at,
import_audit_runs.finished_at,
import_audit_runs.duration_ms,
import_audit_runs.user_id,
import_audit_runs.current_tenant_id,
users.id,
users.uuid,
users.display_name,
users.email,
tenants.id,
tenants.uuid,
tenants.description
' . $fromSql . $whereSql .
sprintf(' order by import_audit_runs.`%s` %s limit ? offset ?', $order, $dir),
...array_merge($params, [(string) $limit, (string) $offset])
);
$normalized = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$item = $this->normalizeRow($row);
if ($item !== null) {
$normalized[] = $item;
}
}
}
return [
'total' => $total,
'rows' => $normalized,
];
}
public function listFilterOptions(int $limit = self::FILTER_OPTIONS_LIMIT_MAX): array
{
$limit = max(1, min(self::FILTER_OPTIONS_LIMIT_MAX, $limit));
$userRows = DB::select(
'select
import_audit_runs.user_id,
max(import_audit_runs.started_at) as last_used_at,
max(users.id) as user_exists_id,
max(users.display_name) as user_display_name,
max(users.email) as user_email
from import_audit_runs
left join users on users.id = import_audit_runs.user_id
where import_audit_runs.user_id is not null
and import_audit_runs.user_id > 0
group by import_audit_runs.user_id
order by last_used_at desc
limit ?',
(string) $limit
);
$users = [];
if (is_array($userRows)) {
foreach ($userRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$id = (int) ($flat['user_id'] ?? 0);
if ($id <= 0) {
continue;
}
$users[] = [
'id' => $id,
'display_name' => trim((string) ($flat['user_display_name'] ?? '')),
'email' => trim((string) ($flat['user_email'] ?? '')),
'exists' => (int) ($flat['user_exists_id'] ?? 0) > 0,
];
}
}
return ['users' => $users];
}
public function find(int $id): ?array
{
if ($id <= 0) {
return null;
}
$row = DB::selectOne(
'select
import_audit_runs.id,
import_audit_runs.run_uuid,
import_audit_runs.profile_key,
import_audit_runs.status,
import_audit_runs.source_filename,
import_audit_runs.mapped_targets_csv,
import_audit_runs.rows_total,
import_audit_runs.created_count,
import_audit_runs.skipped_count,
import_audit_runs.failed_count,
import_audit_runs.error_codes_json,
import_audit_runs.started_at,
import_audit_runs.finished_at,
import_audit_runs.duration_ms,
import_audit_runs.user_id,
import_audit_runs.current_tenant_id,
users.id,
users.uuid,
users.display_name,
users.email,
tenants.id,
tenants.uuid,
tenants.description
from import_audit_runs
left join users on users.id = import_audit_runs.user_id
left join tenants on tenants.id = import_audit_runs.current_tenant_id
where import_audit_runs.id = ?
limit 1',
(string) $id
);
return $this->normalizeRow($row);
}
public function purgeOlderThanDays(int $days): int
{
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 import_audit_runs where started_at < ?', $cutoff);
return is_int($deleted) ? $deleted : 0;
}
private function normalizeRow(mixed $row): ?array
{
if (!is_array($row)) {
return null;
}
$item = $row['import_audit_runs'] ?? [];
if (!is_array($item) || !isset($item['id'])) {
return null;
}
$user = is_array($row['users'] ?? null) ? $row['users'] : [];
$tenant = is_array($row['tenants'] ?? null) ? $row['tenants'] : [];
$item['user_uuid'] = (string) ($user['uuid'] ?? '');
$item['user_display_name'] = (string) ($user['display_name'] ?? '');
$item['user_email'] = (string) ($user['email'] ?? '');
$item['current_tenant_uuid'] = (string) ($tenant['uuid'] ?? '');
$item['current_tenant_description'] = (string) ($tenant['description'] ?? '');
return $item;
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
/** Contract for CSV import run audit trail creation, completion, and purge. */
interface ImportAuditRunRepositoryInterface
{
public function createRunning(array $data): int|false;
public function finishById(int $id, array $data): bool;
public function listPaged(array $filters): array;
public function listFilterOptions(int $limit = 200): array;
public function find(int $id): ?array;
public function purgeOlderThanDays(int $days): int;
}

View File

@@ -1,339 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
use MintyPHP\DB;
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
/** Persists system audit events with actor, target, outcome, channel, and hashed request metadata. */
class SystemAuditLogRepository implements SystemAuditLogRepositoryInterface
{
private const FILTER_OPTIONS_LIMIT_MAX = 200;
public function create(array $data): int|false
{
$id = DB::insert(
'insert into system_audit_log (
event_uuid, request_id, channel, event_type, outcome, error_code,
actor_user_id, actor_tenant_id, target_type, target_id, target_uuid,
method, path, ip_hash, user_agent_hash, metadata_json, created_at
) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())',
(string) ($data['event_uuid'] ?? ''),
$data['request_id'] ?? null,
(string) ($data['channel'] ?? ''),
(string) ($data['event_type'] ?? ''),
(string) ($data['outcome'] ?? SystemAuditOutcome::Success->value),
$data['error_code'] ?? null,
$data['actor_user_id'] !== null ? (string) ((int) $data['actor_user_id']) : null,
$data['actor_tenant_id'] !== null ? (string) ((int) $data['actor_tenant_id']) : null,
$data['target_type'] ?? null,
$data['target_id'] !== null ? (string) ((int) $data['target_id']) : null,
$data['target_uuid'] ?? null,
$data['method'] ?? null,
$data['path'] ?? null,
$data['ip_hash'] ?? null,
$data['user_agent_hash'] ?? null,
$data['metadata_json'] ?? null
);
return $id ? (int) $id : false;
}
public function listPaged(array $filters): array
{
$search = trim((string) ($filters['search'] ?? ''));
$eventTypes = RepoQuery::normalizeStringList(
$filters['event_types'] ?? '',
self::FILTER_OPTIONS_LIMIT_MAX,
static function (string $value): string {
$value = strtolower(trim($value));
if ($value === '' || strlen($value) > 64) {
return '';
}
return preg_match('/^[a-z0-9._-]+$/', $value) === 1 ? $value : '';
}
);
$outcome = strtolower(trim((string) ($filters['outcome'] ?? '')));
$channel = strtolower(trim((string) ($filters['channel'] ?? '')));
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
$createdTo = trim((string) ($filters['created_to'] ?? ''));
$actorUserIds = array_slice(
RepoQuery::normalizeIdList($filters['actor_user_ids'] ?? ''),
0,
self::FILTER_OPTIONS_LIMIT_MAX
);
$targetType = trim((string) ($filters['target_type'] ?? ''));
$requestId = trim((string) ($filters['request_id'] ?? ''));
[$limit, $offset] = RepoQuery::sanitizeLimitOffset($filters, 20, 1, 200, 0);
[$order, $dir] = RepoQuery::sanitizeOrder(
$filters,
['id', 'created_at', 'event_type', 'outcome', 'channel', 'actor_user_id'],
'created_at',
'desc'
);
$where = [];
$params = [];
RepoQuery::addLikeFilter(
$where,
$params,
['system_audit_log.event_type', 'system_audit_log.path', 'system_audit_log.error_code', 'system_audit_log.request_id'],
$search
);
if ($eventTypes !== []) {
$where[] = 'system_audit_log.event_type in (???)';
$params[] = $eventTypes;
}
$normalizedOutcome = SystemAuditOutcome::tryNormalize($outcome);
if ($normalizedOutcome !== null) {
$where[] = 'system_audit_log.outcome = ?';
$params[] = $normalizedOutcome->value;
}
$normalizedChannel = SystemAuditChannel::tryNormalize($channel);
if ($normalizedChannel !== null) {
$where[] = 'system_audit_log.channel = ?';
$params[] = $normalizedChannel->value;
}
if ($actorUserIds !== []) {
$where[] = 'system_audit_log.actor_user_id in (???)';
$params[] = $actorUserIds;
}
if ($targetType !== '') {
$where[] = 'system_audit_log.target_type = ?';
$params[] = $targetType;
}
if ($requestId !== '') {
if (preg_match('/^[a-f0-9-]{36}$/i', $requestId)) {
$where[] = 'system_audit_log.request_id = ?';
$params[] = strtolower($requestId);
} else {
RepoQuery::addLikeFilter($where, $params, ['system_audit_log.request_id'], $requestId);
}
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdFrom)) {
$where[] = 'system_audit_log.created_at >= ?';
$params[] = $createdFrom . ' 00:00:00';
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $createdTo)) {
$where[] = 'system_audit_log.created_at <= ?';
$params[] = $createdTo . ' 23:59:59';
}
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
$fromSql = ' from system_audit_log ' .
'left join users actor_user on actor_user.id = system_audit_log.actor_user_id ' .
'left join tenants actor_tenant on actor_tenant.id = system_audit_log.actor_tenant_id ';
$total = (int) (DB::selectValue('select count(*)' . $fromSql . $whereSql, ...$params) ?? 0);
$rows = DB::select(
'select
system_audit_log.id,
system_audit_log.event_uuid,
system_audit_log.request_id,
system_audit_log.channel,
system_audit_log.event_type,
system_audit_log.outcome,
system_audit_log.error_code,
system_audit_log.actor_user_id,
system_audit_log.actor_tenant_id,
system_audit_log.target_type,
system_audit_log.target_id,
system_audit_log.target_uuid,
system_audit_log.method,
system_audit_log.path,
system_audit_log.ip_hash,
system_audit_log.user_agent_hash,
system_audit_log.metadata_json,
system_audit_log.created_at,
actor_user.id,
actor_user.uuid,
actor_user.display_name,
actor_user.email,
actor_tenant.id,
actor_tenant.uuid,
actor_tenant.description
' . $fromSql . $whereSql .
sprintf(' order by system_audit_log.`%s` %s limit ? offset ?', $order, $dir),
...array_merge($params, [(string) $limit, (string) $offset])
);
$normalized = [];
if (is_array($rows)) {
foreach ($rows as $row) {
$item = $this->normalizeRow($row);
if ($item !== null) {
$normalized[] = $item;
}
}
}
return [
'total' => $total,
'rows' => $normalized,
];
}
public function listFilterOptions(int $limit = self::FILTER_OPTIONS_LIMIT_MAX): array
{
$limit = max(1, min(self::FILTER_OPTIONS_LIMIT_MAX, $limit));
$eventRows = DB::select(
'select
system_audit_log.event_type,
max(system_audit_log.created_at) as last_used_at
from system_audit_log
where system_audit_log.event_type is not null
and system_audit_log.event_type <> \'\'
group by system_audit_log.event_type
order by last_used_at desc
limit ?',
(string) $limit
);
$eventTypes = [];
if (is_array($eventRows)) {
foreach ($eventRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$eventType = strtolower(trim((string) ($flat['event_type'] ?? '')));
if ($eventType === '' || strlen($eventType) > 64) {
continue;
}
if (preg_match('/^[a-z0-9._-]+$/', $eventType) !== 1) {
continue;
}
$eventTypes[] = $eventType;
}
}
$eventTypes = array_values(array_unique($eventTypes));
$actorRows = DB::select(
'select
system_audit_log.actor_user_id,
max(system_audit_log.created_at) as last_used_at,
max(actor_user.id) as actor_user_exists_id,
max(actor_user.display_name) as actor_user_display_name,
max(actor_user.email) as actor_user_email
from system_audit_log
left join users actor_user on actor_user.id = system_audit_log.actor_user_id
where system_audit_log.actor_user_id is not null
and system_audit_log.actor_user_id > 0
group by system_audit_log.actor_user_id
order by last_used_at desc
limit ?',
(string) $limit
);
$actors = [];
if (is_array($actorRows)) {
foreach ($actorRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$id = (int) ($flat['actor_user_id'] ?? 0);
if ($id <= 0) {
continue;
}
$actors[] = [
'id' => $id,
'display_name' => trim((string) ($flat['actor_user_display_name'] ?? '')),
'email' => trim((string) ($flat['actor_user_email'] ?? '')),
'exists' => (int) ($flat['actor_user_exists_id'] ?? 0) > 0,
];
}
}
return [
'event_types' => $eventTypes,
'actors' => $actors,
];
}
public function find(int $id): ?array
{
$row = DB::selectOne(
'select
system_audit_log.id,
system_audit_log.event_uuid,
system_audit_log.request_id,
system_audit_log.channel,
system_audit_log.event_type,
system_audit_log.outcome,
system_audit_log.error_code,
system_audit_log.actor_user_id,
system_audit_log.actor_tenant_id,
system_audit_log.target_type,
system_audit_log.target_id,
system_audit_log.target_uuid,
system_audit_log.method,
system_audit_log.path,
system_audit_log.ip_hash,
system_audit_log.user_agent_hash,
system_audit_log.metadata_json,
system_audit_log.created_at,
actor_user.id,
actor_user.uuid,
actor_user.display_name,
actor_user.email,
actor_tenant.id,
actor_tenant.uuid,
actor_tenant.description
from system_audit_log
left join users actor_user on actor_user.id = system_audit_log.actor_user_id
left join tenants actor_tenant on actor_tenant.id = system_audit_log.actor_tenant_id
where system_audit_log.id = ?
limit 1',
(string) $id
);
return $this->normalizeRow($row);
}
public function purgeOlderThanDays(int $days): int
{
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 system_audit_log where created_at < ?', $cutoff);
return is_int($deleted) ? $deleted : 0;
}
private function normalizeRow(mixed $row): ?array
{
if (!is_array($row)) {
return null;
}
$log = $row['system_audit_log'] ?? [];
if (!is_array($log) || !isset($log['id'])) {
return null;
}
$user = is_array($row['actor_user'] ?? null) ? $row['actor_user'] : [];
$tenant = is_array($row['actor_tenant'] ?? null) ? $row['actor_tenant'] : [];
$log['actor_user_uuid'] = (string) ($user['uuid'] ?? '');
$log['actor_user_display_name'] = (string) ($user['display_name'] ?? '');
$log['actor_user_email'] = (string) ($user['email'] ?? '');
$log['actor_tenant_uuid'] = (string) ($tenant['uuid'] ?? '');
$log['actor_tenant_description'] = (string) ($tenant['description'] ?? '');
return $log;
}
}

View File

@@ -1,17 +0,0 @@
<?php
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;
public function listPaged(array $filters): array;
public function listFilterOptions(int $limit = 200): array;
public function find(int $id): ?array;
public function purgeOlderThanDays(int $days): int;
}

View File

@@ -1,449 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
use MintyPHP\DB;
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
use MintyPHP\Domain\Taxonomy\UserLifecycleTriggerType;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
/** Records user lifecycle transitions (deactivation, deletion, restore) with reason codes and snapshots. */
class UserLifecycleAuditRepository implements UserLifecycleAuditRepositoryInterface
{
private const FILTER_OPTIONS_LIMIT_MAX = 200;
public function create(array $row): int|false
{
$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;
}
public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool
{
if ($id <= 0) {
return false;
}
$normalizedStatus = UserLifecycleStatus::tryNormalize($status);
if ($normalizedStatus === null) {
return false;
}
$updated = DB::update(
'update user_lifecycle_audit_log set status = ?, reason_code = ? where id = ?',
$normalizedStatus->value,
$reasonCode,
(string) $id
);
return $updated !== false;
}
public function listPaged(array $filters): array
{
$search = trim((string) ($filters['search'] ?? ''));
$actions = RepoQuery::normalizeStringList(
$filters['actions'] ?? '',
self::FILTER_OPTIONS_LIMIT_MAX,
static fn (string $value): string => UserLifecycleAction::tryNormalize($value)?->value ?? ''
);
$statuses = RepoQuery::normalizeStringList(
$filters['statuses'] ?? '',
self::FILTER_OPTIONS_LIMIT_MAX,
static fn (string $value): string => UserLifecycleStatus::tryNormalize($value)?->value ?? ''
);
$triggerTypes = RepoQuery::normalizeStringList(
$filters['trigger_types'] ?? '',
self::FILTER_OPTIONS_LIMIT_MAX,
static fn (string $value): string => UserLifecycleTriggerType::tryNormalize($value)?->value ?? ''
);
$actorUserIds = array_slice(
RepoQuery::normalizeIdList($filters['actor_user_ids'] ?? ''),
0,
self::FILTER_OPTIONS_LIMIT_MAX
);
$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
);
if ($actions !== []) {
$where[] = 'user_lifecycle_audit_log.action in (???)';
$params[] = $actions;
}
if ($statuses !== []) {
$where[] = 'user_lifecycle_audit_log.status in (???)';
$params[] = $statuses;
}
if ($triggerTypes !== []) {
$where[] = 'user_lifecycle_audit_log.trigger_type in (???)';
$params[] = $triggerTypes;
}
if ($actorUserIds !== []) {
$where[] = 'user_lifecycle_audit_log.actor_user_id in (???)';
$params[] = $actorUserIds;
}
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) {
$item = $this->normalizeRow($row, false);
if ($item !== null) {
$normalized[] = $item;
}
}
}
return ['total' => $total, 'rows' => $normalized];
}
public function listFilterOptions(int $limit = self::FILTER_OPTIONS_LIMIT_MAX): array
{
$limit = max(1, min(self::FILTER_OPTIONS_LIMIT_MAX, $limit));
$actionRows = DB::select(
'select
user_lifecycle_audit_log.action,
max(user_lifecycle_audit_log.created_at) as last_used_at
from user_lifecycle_audit_log
where user_lifecycle_audit_log.action is not null
and user_lifecycle_audit_log.action <> \'\'
group by user_lifecycle_audit_log.action
order by last_used_at desc
limit ?',
(string) $limit
);
$actions = [];
if (is_array($actionRows)) {
foreach ($actionRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$action = strtolower(trim((string) ($flat['action'] ?? '')));
if (UserLifecycleAction::tryNormalize($action) === null) {
continue;
}
$actions[] = $action;
}
}
$actions = array_values(array_unique($actions));
$statusRows = DB::select(
'select
user_lifecycle_audit_log.status,
max(user_lifecycle_audit_log.created_at) as last_used_at
from user_lifecycle_audit_log
where user_lifecycle_audit_log.status is not null
and user_lifecycle_audit_log.status <> \'\'
group by user_lifecycle_audit_log.status
order by last_used_at desc
limit ?',
(string) $limit
);
$statuses = [];
if (is_array($statusRows)) {
foreach ($statusRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$status = strtolower(trim((string) ($flat['status'] ?? '')));
if (UserLifecycleStatus::tryNormalize($status) === null) {
continue;
}
$statuses[] = $status;
}
}
$statuses = array_values(array_unique($statuses));
$triggerRows = DB::select(
'select
user_lifecycle_audit_log.trigger_type,
max(user_lifecycle_audit_log.created_at) as last_used_at
from user_lifecycle_audit_log
where user_lifecycle_audit_log.trigger_type is not null
and user_lifecycle_audit_log.trigger_type <> \'\'
group by user_lifecycle_audit_log.trigger_type
order by last_used_at desc
limit ?',
(string) $limit
);
$triggerTypes = [];
if (is_array($triggerRows)) {
foreach ($triggerRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$triggerType = strtolower(trim((string) ($flat['trigger_type'] ?? '')));
if (UserLifecycleTriggerType::tryNormalize($triggerType) === null) {
continue;
}
$triggerTypes[] = $triggerType;
}
}
$triggerTypes = array_values(array_unique($triggerTypes));
$actorRows = DB::select(
'select
user_lifecycle_audit_log.actor_user_id,
max(user_lifecycle_audit_log.created_at) as last_used_at,
max(actor_user.id) as actor_user_exists_id,
max(actor_user.display_name) as actor_user_display_name,
max(actor_user.email) as actor_user_email
from user_lifecycle_audit_log
left join users actor_user on actor_user.id = user_lifecycle_audit_log.actor_user_id
where user_lifecycle_audit_log.actor_user_id is not null
and user_lifecycle_audit_log.actor_user_id > 0
group by user_lifecycle_audit_log.actor_user_id
order by last_used_at desc
limit ?',
(string) $limit
);
$actors = [];
if (is_array($actorRows)) {
foreach ($actorRows as $row) {
$flat = RepositoryArrayHelper::flattenRow($row);
$id = (int) ($flat['actor_user_id'] ?? 0);
if ($id <= 0) {
continue;
}
$actors[] = [
'id' => $id,
'display_name' => trim((string) ($flat['actor_user_display_name'] ?? '')),
'email' => trim((string) ($flat['actor_user_email'] ?? '')),
'exists' => (int) ($flat['actor_user_exists_id'] ?? 0) > 0,
];
}
}
return [
'actions' => $actions,
'statuses' => $statuses,
'trigger_types' => $triggerTypes,
'actors' => $actors,
];
}
public function find(int $id): ?array
{
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
);
return $this->normalizeRow($row, true);
}
public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array
{
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;
}
public function markRestored(int $id, int $restoredBy, int $restoredUserId): bool
{
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;
}
public function purgeOlderThanDays(int $days): int
{
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;
}
private function normalizeRow(mixed $row, bool $includeSnapshot): ?array
{
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;
}
}

View File

@@ -1,23 +0,0 @@
<?php
namespace MintyPHP\Repository\Audit;
/** Contract for tracking user deactivation/deletion lifecycle events and restore eligibility. */
interface UserLifecycleAuditRepositoryInterface
{
public function create(array $row): int|false;
public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool;
public function listPaged(array $filters): array;
public function listFilterOptions(int $limit = 200): array;
public function find(int $id): ?array;
public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array;
public function markRestored(int $id, int $restoredBy, int $restoredUserId): bool;
public function purgeOlderThanDays(int $days): int;
}

View File

@@ -3,14 +3,10 @@
namespace MintyPHP\Repository\Stats;
use MintyPHP\DB;
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
use MintyPHP\Domain\Taxonomy\MailLogStatus;
use MintyPHP\Domain\Taxonomy\ScheduledJobRunStatus;
use MintyPHP\Domain\Taxonomy\ScheduledJobTriggerType;
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
use MintyPHP\Domain\Taxonomy\TenantStatus;
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
class AdminStatsRepository implements AdminStatsRepositoryInterface
{
@@ -21,15 +17,15 @@ class AdminStatsRepository implements AdminStatsRepositoryInterface
{
$tenantActiveStatus = TenantStatus::Active->value;
$tenantInactiveStatus = TenantStatus::Inactive->value;
$importStatusRunning = ImportAuditStatus::Running->value;
$importStatusSuccess = ImportAuditStatus::Success->value;
$importStatusPartial = ImportAuditStatus::Partial->value;
$importStatusFailed = ImportAuditStatus::Failed->value;
$systemAuditOutcomeFailed = SystemAuditOutcome::Failed->value;
$systemAuditOutcomeDenied = SystemAuditOutcome::Denied->value;
$lifecycleStatusFailed = UserLifecycleStatus::Failed->value;
$lifecycleStatusSkipped = UserLifecycleStatus::Skipped->value;
$lifecycleActionRestore = UserLifecycleAction::Restore->value;
$importStatusRunning = 'running';
$importStatusSuccess = 'success';
$importStatusPartial = 'partial';
$importStatusFailed = 'failed';
$systemAuditOutcomeFailed = 'failed';
$systemAuditOutcomeDenied = 'denied';
$lifecycleStatusFailed = 'failed';
$lifecycleStatusSkipped = 'skipped';
$lifecycleActionRestore = 'restore';
$schedulerTriggerScheduler = ScheduledJobTriggerType::Scheduler->value;
$schedulerRunStatusFailed = ScheduledJobRunStatus::Failed->value;
$mailStatusFailed = MailLogStatus::Failed->value;