major update
This commit is contained in:
@@ -76,7 +76,7 @@ class PermissionRepository
|
||||
$list[] = $data;
|
||||
}
|
||||
}
|
||||
return ['data' => $list, 'total' => (int) $total];
|
||||
return ['rows' => $list, 'total' => (int) $total];
|
||||
}
|
||||
|
||||
public function find(int $id): ?array
|
||||
|
||||
@@ -40,6 +40,34 @@ class UserRoleRepository
|
||||
return true;
|
||||
}
|
||||
|
||||
public function listUserIdsByRoleIds(array $roleIds): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_map('intval', $roleIds)));
|
||||
$ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
|
||||
if (!$ids) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = DB::select(
|
||||
'select distinct user_id from user_roles where role_id in (???)',
|
||||
array_map('strval', $ids)
|
||||
);
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$data = $row['user_roles'] ?? $row;
|
||||
if (!is_array($data) || !isset($data['user_id'])) {
|
||||
continue;
|
||||
}
|
||||
$userIds[] = (int) $data['user_id'];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($userIds, static fn ($id) => $id > 0)));
|
||||
}
|
||||
|
||||
public function countUsersByRoleIds(array $roleIds): array
|
||||
{
|
||||
return $this->countByRoleIds($roleIds, false);
|
||||
|
||||
@@ -7,6 +7,8 @@ use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
class ApiAuditLogRepository
|
||||
{
|
||||
private const FILTER_OPTIONS_LIMIT_MAX = 200;
|
||||
|
||||
public function create(array $data): int|false
|
||||
{
|
||||
$id = DB::insert(
|
||||
@@ -39,8 +41,16 @@ class ApiAuditLogRepository
|
||||
$method = strtoupper(trim((string) ($filters['method'] ?? '')));
|
||||
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
|
||||
$createdTo = trim((string) ($filters['created_to'] ?? ''));
|
||||
$tenantId = (int) ($filters['tenant_id'] ?? 0);
|
||||
$userId = (int) ($filters['user_id'] ?? 0);
|
||||
$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(
|
||||
@@ -86,13 +96,13 @@ class ApiAuditLogRepository
|
||||
$where[] = 'api_audit_log.created_at <= ?';
|
||||
$params[] = $createdTo . ' 23:59:59';
|
||||
}
|
||||
if ($tenantId > 0) {
|
||||
$where[] = 'api_audit_log.tenant_id = ?';
|
||||
$params[] = (string) $tenantId;
|
||||
if ($tenantIds !== []) {
|
||||
$where[] = 'api_audit_log.tenant_id in (???)';
|
||||
$params[] = $tenantIds;
|
||||
}
|
||||
if ($userId > 0) {
|
||||
$where[] = 'api_audit_log.user_id = ?';
|
||||
$params[] = (string) $userId;
|
||||
if ($userIds !== []) {
|
||||
$where[] = 'api_audit_log.user_id in (???)';
|
||||
$params[] = $userIds;
|
||||
}
|
||||
|
||||
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
|
||||
@@ -147,6 +157,109 @@ class ApiAuditLogRepository
|
||||
];
|
||||
}
|
||||
|
||||
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 = self::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 = self::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 = self::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(
|
||||
@@ -219,4 +332,25 @@ class ApiAuditLogRepository
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function flattenRow(mixed $row): array
|
||||
{
|
||||
if (!is_array($row)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
foreach ($row as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$flat = array_merge($flat, $value);
|
||||
} elseif (is_string($key)) {
|
||||
$flat[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ImportAuditStatus;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
class ImportAuditRunRepository
|
||||
{
|
||||
private const FILTER_OPTIONS_LIMIT_MAX = 200;
|
||||
|
||||
public function createRunning(array $data): int|false
|
||||
{
|
||||
$id = DB::insert(
|
||||
@@ -15,7 +18,7 @@ class ImportAuditRunRepository
|
||||
) values (?,?,?,?,?,NOW(),?,?)',
|
||||
(string) ($data['run_uuid'] ?? ''),
|
||||
(string) ($data['profile_key'] ?? ''),
|
||||
(string) ($data['status'] ?? 'running'),
|
||||
(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,
|
||||
@@ -42,7 +45,7 @@ class ImportAuditRunRepository
|
||||
duration_ms = ?,
|
||||
finished_at = NOW()
|
||||
where id = ?',
|
||||
(string) ($data['status'] ?? 'failed'),
|
||||
(string) ($data['status'] ?? ImportAuditStatus::Failed->value),
|
||||
(string) ((int) ($data['rows_total'] ?? 0)),
|
||||
(string) ((int) ($data['created_count'] ?? 0)),
|
||||
(string) ((int) ($data['skipped_count'] ?? 0)),
|
||||
@@ -62,7 +65,11 @@ class ImportAuditRunRepository
|
||||
$status = strtolower(trim((string) ($filters['status'] ?? '')));
|
||||
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
|
||||
$createdTo = trim((string) ($filters['created_to'] ?? ''));
|
||||
$userId = (int) ($filters['user_id'] ?? 0);
|
||||
$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(
|
||||
@@ -101,7 +108,7 @@ class ImportAuditRunRepository
|
||||
$where[] = 'import_audit_runs.profile_key = ?';
|
||||
$params[] = $profileKey;
|
||||
}
|
||||
if (in_array($status, ['running', 'success', 'partial', 'failed'], true)) {
|
||||
if (ImportAuditStatus::tryNormalize($status) !== null) {
|
||||
$where[] = 'import_audit_runs.status = ?';
|
||||
$params[] = $status;
|
||||
}
|
||||
@@ -113,9 +120,9 @@ class ImportAuditRunRepository
|
||||
$where[] = 'import_audit_runs.started_at <= ?';
|
||||
$params[] = $createdTo . ' 23:59:59';
|
||||
}
|
||||
if ($userId > 0) {
|
||||
$where[] = 'import_audit_runs.user_id = ?';
|
||||
$params[] = (string) $userId;
|
||||
if ($userIds !== []) {
|
||||
$where[] = 'import_audit_runs.user_id in (???)';
|
||||
$params[] = $userIds;
|
||||
}
|
||||
|
||||
$whereSql = $where ? (' where ' . implode(' and ', $where)) : '';
|
||||
@@ -170,6 +177,47 @@ class ImportAuditRunRepository
|
||||
];
|
||||
}
|
||||
|
||||
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 = self::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) {
|
||||
@@ -248,4 +296,25 @@ class ImportAuditRunRepository
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function flattenRow(mixed $row): array
|
||||
{
|
||||
if (!is_array($row)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
foreach ($row as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$flat = array_merge($flat, $value);
|
||||
} elseif (is_string($key)) {
|
||||
$flat[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
}
|
||||
|
||||
357
lib/Repository/Audit/SystemAuditLogRepository.php
Normal file
357
lib/Repository/Audit/SystemAuditLogRepository.php
Normal file
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditChannel;
|
||||
use MintyPHP\Domain\Taxonomy\SystemAuditOutcome;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
class SystemAuditLogRepository
|
||||
{
|
||||
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 = self::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 = self::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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function flattenRow(mixed $row): array
|
||||
{
|
||||
if (!is_array($row)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
foreach ($row as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$flat = array_merge($flat, $value);
|
||||
} elseif (is_string($key)) {
|
||||
$flat[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
namespace MintyPHP\Repository\Audit;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleAction;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleStatus;
|
||||
use MintyPHP\Domain\Taxonomy\UserLifecycleTriggerType;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
class UserLifecycleAuditRepository
|
||||
{
|
||||
private const FILTER_OPTIONS_LIMIT_MAX = 200;
|
||||
|
||||
public function create(array $row): int|false
|
||||
{
|
||||
$id = DB::insert(
|
||||
@@ -38,14 +43,14 @@ class UserLifecycleAuditRepository
|
||||
if ($id <= 0) {
|
||||
return false;
|
||||
}
|
||||
$status = trim(strtolower($status));
|
||||
if (!in_array($status, ['success', 'skipped', 'failed'], true)) {
|
||||
$normalizedStatus = UserLifecycleStatus::tryNormalize($status);
|
||||
if ($normalizedStatus === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updated = DB::update(
|
||||
'update user_lifecycle_audit_log set status = ?, reason_code = ? where id = ?',
|
||||
$status,
|
||||
$normalizedStatus->value,
|
||||
$reasonCode,
|
||||
(string) $id
|
||||
);
|
||||
@@ -55,9 +60,26 @@ class UserLifecycleAuditRepository
|
||||
public function listPaged(array $filters): array
|
||||
{
|
||||
$search = trim((string) ($filters['search'] ?? ''));
|
||||
$action = strtolower(trim((string) ($filters['action'] ?? '')));
|
||||
$status = strtolower(trim((string) ($filters['status'] ?? '')));
|
||||
$triggerType = strtolower(trim((string) ($filters['trigger_type'] ?? '')));
|
||||
$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'] ?? ''));
|
||||
|
||||
@@ -82,17 +104,21 @@ class UserLifecycleAuditRepository
|
||||
],
|
||||
$search
|
||||
);
|
||||
if (in_array($action, ['deactivate', 'delete', 'restore'], true)) {
|
||||
$where[] = 'user_lifecycle_audit_log.action = ?';
|
||||
$params[] = $action;
|
||||
if ($actions !== []) {
|
||||
$where[] = 'user_lifecycle_audit_log.action in (???)';
|
||||
$params[] = $actions;
|
||||
}
|
||||
if (in_array($status, ['success', 'skipped', 'failed'], true)) {
|
||||
$where[] = 'user_lifecycle_audit_log.status = ?';
|
||||
$params[] = $status;
|
||||
if ($statuses !== []) {
|
||||
$where[] = 'user_lifecycle_audit_log.status in (???)';
|
||||
$params[] = $statuses;
|
||||
}
|
||||
if (in_array($triggerType, ['manual', 'cron', 'system'], true)) {
|
||||
$where[] = 'user_lifecycle_audit_log.trigger_type = ?';
|
||||
$params[] = $triggerType;
|
||||
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 >= ?';
|
||||
@@ -157,6 +183,130 @@ class UserLifecycleAuditRepository
|
||||
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 = self::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 = self::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 = self::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 = self::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) {
|
||||
@@ -293,4 +443,25 @@ class UserLifecycleAuditRepository
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function flattenRow(mixed $row): array
|
||||
{
|
||||
if (!is_array($row)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
foreach ($row as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$flat = array_merge($flat, $value);
|
||||
} elseif (is_string($key)) {
|
||||
$flat[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MintyPHP\Repository\Mail;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\MailLogStatus;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
@@ -14,7 +15,7 @@ class MailLogRepository
|
||||
$data['to_email'],
|
||||
$data['subject'],
|
||||
$data['template'] ?? null,
|
||||
$data['status'] ?? 'queued'
|
||||
$data['status'] ?? MailLogStatus::Queued->value
|
||||
);
|
||||
return $id ? (int) $id : null;
|
||||
}
|
||||
@@ -23,7 +24,7 @@ class MailLogRepository
|
||||
{
|
||||
$result = DB::update(
|
||||
'update mail_log set status = ?, sent_at = NOW(), provider_message_id = ?, error_message = NULL where id = ?',
|
||||
'sent',
|
||||
MailLogStatus::Sent->value,
|
||||
$providerMessageId,
|
||||
(string) $id
|
||||
);
|
||||
@@ -34,7 +35,7 @@ class MailLogRepository
|
||||
{
|
||||
$result = DB::update(
|
||||
'update mail_log set status = ?, error_message = ? where id = ?',
|
||||
'failed',
|
||||
MailLogStatus::Failed->value,
|
||||
$errorMessage,
|
||||
(string) $id
|
||||
);
|
||||
@@ -44,7 +45,7 @@ class MailLogRepository
|
||||
public function listPaged(array $options): array
|
||||
{
|
||||
$search = trim((string) ($options['search'] ?? ''));
|
||||
$status = trim((string) ($options['status'] ?? ''));
|
||||
$status = MailLogStatus::tryNormalize((string) ($options['status'] ?? ''))?->value ?? '';
|
||||
$createdFrom = trim((string) ($options['created_from'] ?? ''));
|
||||
$createdTo = trim((string) ($options['created_to'] ?? ''));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MintyPHP\Repository\Scheduler;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobStatus;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
@@ -75,7 +76,7 @@ class ScheduledJobRepository
|
||||
$where[] = 'enabled = ?';
|
||||
$params[] = $enabled;
|
||||
}
|
||||
if (in_array($status, ['success', 'failed', 'running', 'skipped'], true)) {
|
||||
if (ScheduledJobStatus::tryNormalize($status) !== null) {
|
||||
$where[] = 'last_run_status = ?';
|
||||
$params[] = $status;
|
||||
}
|
||||
@@ -197,10 +198,10 @@ class ScheduledJobRepository
|
||||
and (last_run_status is null
|
||||
or last_run_status <> ?
|
||||
or (last_run_started_at is not null and last_run_started_at < ?))',
|
||||
'running',
|
||||
ScheduledJobStatus::Running->value,
|
||||
$startedAtUtc,
|
||||
(string) $id,
|
||||
'running',
|
||||
ScheduledJobStatus::Running->value,
|
||||
$staleBefore
|
||||
);
|
||||
return (int) $updated > 0;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace MintyPHP\Repository\Scheduler;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobRunStatus;
|
||||
use MintyPHP\Domain\Taxonomy\ScheduledJobTriggerType;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
@@ -17,8 +19,8 @@ class ScheduledJobRunRepository
|
||||
(string) ($data['run_uuid'] ?? ''),
|
||||
(string) ((int) ($data['job_id'] ?? 0)),
|
||||
(string) ($data['job_key'] ?? ''),
|
||||
(string) ($data['trigger_type'] ?? 'scheduler'),
|
||||
(string) ($data['status'] ?? 'failed'),
|
||||
(string) ($data['trigger_type'] ?? ScheduledJobTriggerType::Scheduler->value),
|
||||
(string) ($data['status'] ?? ScheduledJobRunStatus::Failed->value),
|
||||
($data['actor_user_id'] ?? null) !== null ? (string) ((int) $data['actor_user_id']) : null,
|
||||
(string) ($data['started_at'] ?? ''),
|
||||
$data['finished_at'] ?? null,
|
||||
@@ -56,11 +58,11 @@ class ScheduledJobRunRepository
|
||||
['scheduled_job_runs.run_uuid', 'scheduled_job_runs.error_code', 'scheduled_job_runs.error_message'],
|
||||
$search
|
||||
);
|
||||
if (in_array($status, ['success', 'failed', 'skipped'], true)) {
|
||||
if (ScheduledJobRunStatus::tryNormalize($status) !== null) {
|
||||
$where[] = 'scheduled_job_runs.status = ?';
|
||||
$params[] = $status;
|
||||
}
|
||||
if (in_array($triggerType, ['scheduler', 'manual'], true)) {
|
||||
if (ScheduledJobTriggerType::tryNormalize($triggerType) !== null) {
|
||||
$where[] = 'scheduled_job_runs.trigger_type = ?';
|
||||
$params[] = $triggerType;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MintyPHP\Repository\Scheduler;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\SchedulerRuntimeResult;
|
||||
use MintyPHP\DB;
|
||||
|
||||
class SchedulerRuntimeRepository
|
||||
@@ -13,10 +14,7 @@ class SchedulerRuntimeRepository
|
||||
$heartbeatAtUtc = gmdate('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$result = trim($result);
|
||||
if ($result === '') {
|
||||
$result = 'ok';
|
||||
}
|
||||
$result = SchedulerRuntimeResult::normalizeOr($result, SchedulerRuntimeResult::Ok)->value;
|
||||
$errorCode = $this->nullableTrim($errorCode);
|
||||
|
||||
$updated = DB::update(
|
||||
|
||||
37
lib/Repository/Search/SearchQueryRepository.php
Normal file
37
lib/Repository/Search/SearchQueryRepository.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Search;
|
||||
|
||||
use MintyPHP\DB;
|
||||
|
||||
class SearchQueryRepository
|
||||
{
|
||||
/**
|
||||
* @param array<int, mixed> $params
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public function selectRows(string $sql, array $params = []): array
|
||||
{
|
||||
if (trim($sql) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = DB::select($sql, ...$params);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $params
|
||||
*/
|
||||
public function selectCount(string $sql, array $params = []): int
|
||||
{
|
||||
if (trim($sql) === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$value = DB::selectValue($sql, ...$params);
|
||||
|
||||
return (int) ($value ?? 0);
|
||||
}
|
||||
}
|
||||
1090
lib/Repository/Stats/AdminStatsRepository.php
Normal file
1090
lib/Repository/Stats/AdminStatsRepository.php
Normal file
File diff suppressed because it is too large
Load Diff
50
lib/Repository/Support/DatabaseSessionRepository.php
Normal file
50
lib/Repository/Support/DatabaseSessionRepository.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Repository\Support;
|
||||
|
||||
use MintyPHP\DB;
|
||||
|
||||
class DatabaseSessionRepository
|
||||
{
|
||||
public function acquireAdvisoryLock(string $lockName, int $timeoutSeconds = 0): bool
|
||||
{
|
||||
$lockName = trim($lockName);
|
||||
if ($lockName === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timeoutSeconds = max(0, (int) $timeoutSeconds);
|
||||
$got = DB::selectValue(
|
||||
'select GET_LOCK(?, ?) as got_lock',
|
||||
$lockName,
|
||||
(string) $timeoutSeconds
|
||||
);
|
||||
|
||||
return (int) $got === 1;
|
||||
}
|
||||
|
||||
public function releaseAdvisoryLock(string $lockName): void
|
||||
{
|
||||
$lockName = trim($lockName);
|
||||
if ($lockName === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::selectValue('select RELEASE_LOCK(?) as released_lock', $lockName);
|
||||
}
|
||||
|
||||
public function beginTransaction(): void
|
||||
{
|
||||
DB::handle()->begin_transaction();
|
||||
}
|
||||
|
||||
public function commitTransaction(): void
|
||||
{
|
||||
DB::handle()->commit();
|
||||
}
|
||||
|
||||
public function rollbackTransaction(): void
|
||||
{
|
||||
DB::handle()->rollback();
|
||||
}
|
||||
}
|
||||
@@ -148,4 +148,50 @@ class RepoQuery
|
||||
sort($ids, SORT_NUMERIC);
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public static function normalizeStringList(mixed $value, int $max = 200, ?callable $sanitizer = null): array
|
||||
{
|
||||
if ($max <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = array_filter(array_map('trim', explode(',', $value)));
|
||||
} elseif (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flat = [];
|
||||
array_walk_recursive($value, static function ($item) use (&$flat): void {
|
||||
$flat[] = $item;
|
||||
});
|
||||
|
||||
$items = [];
|
||||
$seen = [];
|
||||
foreach ($flat as $item) {
|
||||
$text = trim((string) $item);
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sanitizer !== null) {
|
||||
$text = trim((string) $sanitizer($text));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($seen[$text])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[$text] = true;
|
||||
$items[] = $text;
|
||||
if (count($items) >= $max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MintyPHP\Repository\Tenant;
|
||||
|
||||
use MintyPHP\Domain\Taxonomy\TenantStatus;
|
||||
use MintyPHP\DB;
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
@@ -81,7 +82,7 @@ class TenantRepository
|
||||
$rows = call_user_func_array(
|
||||
['MintyPHP\\DB', 'select'],
|
||||
array_merge(
|
||||
["select id from tenants where status = 'active' and id in ($placeholders)"],
|
||||
["select id from tenants where status = ? and id in ($placeholders)", TenantStatus::Active->value],
|
||||
$params
|
||||
)
|
||||
);
|
||||
@@ -101,6 +102,7 @@ class TenantRepository
|
||||
public function listPaged(array $options): array
|
||||
{
|
||||
$search = trim((string) ($options['search'] ?? ''));
|
||||
$status = TenantStatus::tryNormalize((string) ($options['status'] ?? ''));
|
||||
$allowedOrder = ['id', 'uuid', 'description', 'status', 'created', 'modified'];
|
||||
[$limit, $offset] = RepoQuery::sanitizeLimitOffset($options);
|
||||
[$order, $dir] = RepoQuery::sanitizeOrder($options, $allowedOrder);
|
||||
@@ -108,6 +110,10 @@ class TenantRepository
|
||||
$where = [];
|
||||
$params = [];
|
||||
RepoQuery::addLikeFilter($where, $params, ['description', 'uuid'], $search);
|
||||
if ($status !== null) {
|
||||
$where[] = 'tenants.status = ?';
|
||||
$params[] = $status->value;
|
||||
}
|
||||
if (!empty($options['tenantUserId'])) {
|
||||
$tenantUserId = (int) $options['tenantUserId'];
|
||||
if ($tenantUserId > 0) {
|
||||
@@ -187,7 +193,7 @@ class TenantRepository
|
||||
$data['primary_color'] ?? null,
|
||||
$data['default_theme'] ?? null,
|
||||
$data['allow_user_theme'] ?? null,
|
||||
$data['status'] ?? 'active',
|
||||
$data['status'] ?? TenantStatus::Active->value,
|
||||
$data['status_changed_at'] ?? null,
|
||||
$data['status_changed_by'] ?? null,
|
||||
$data['created_by'] ?? null
|
||||
@@ -217,7 +223,7 @@ class TenantRepository
|
||||
'primary_color' => $data['primary_color'] ?? null,
|
||||
'default_theme' => $data['default_theme'] ?? null,
|
||||
'allow_user_theme' => $data['allow_user_theme'] ?? null,
|
||||
'status' => $data['status'] ?? 'active',
|
||||
'status' => $data['status'] ?? TenantStatus::Active->value,
|
||||
];
|
||||
if (array_key_exists('modified_by', $data)) {
|
||||
$fields['modified_by'] = $data['modified_by'];
|
||||
|
||||
Reference in New Issue
Block a user