refactor: 更新权限管理与请求验证逻辑
- 在多个控制器中将权限检查从 hasAdminPermission 更新为 hasPermissionCode,以增强权限管理的灵活性。 - 引入 AdminScopePolicy,优化基于代理节点的权限和数据过滤逻辑,确保管理员能够更精确地控制访问权限。 - 在请求验证中添加 agent_node_id 字段,确保 API 接口支持代理节点的相关操作。 - 更新 AdminUser 模型,新增 hasPermissionCode 方法,以支持更细粒度的权限检查。 - 优化审计日志记录逻辑,确保在处理请求时能够准确记录管理员的操作。
This commit is contained in:
@@ -69,7 +69,7 @@ final class AdminAgentScope
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $admin->hasAdminPermission('prd.agent.manage')) {
|
||||
if (! $admin->hasPermissionCode('agent.node.manage')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ final class AdminAuthProfile
|
||||
* depth: int
|
||||
* },
|
||||
* is_super_admin: bool,
|
||||
* operational_permissions: list<string>,
|
||||
* delegation_ceiling: list<string>
|
||||
* }
|
||||
*/
|
||||
@@ -49,6 +50,7 @@ final class AdminAuthProfile
|
||||
'navigation' => AdminAuthorizationRegistry::visibleNavigationItems($permissionSlugs, $fresh),
|
||||
'agent' => self::agentContext($fresh),
|
||||
'is_super_admin' => $fresh->isSuperAdmin(),
|
||||
'operational_permissions' => $permissionSlugs,
|
||||
'delegation_ceiling' => AgentDelegationAuthorization::delegationLegacySlugsForAdminUser($fresh),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -379,7 +379,6 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.admin-users.update', 'module_code' => 'system', 'name' => '更新管理员', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}', 'route_name' => 'api.v1.admin.admin-users.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['code' => 'admin.admin-users.destroy', 'module_code' => 'system', 'name' => '删除管理员', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}', 'route_name' => 'api.v1.admin.admin-users.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['code' => 'admin.admin-users.permission-catalog', 'module_code' => 'system', 'name' => '管理员权限目录', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-user-permission-catalog', 'route_name' => 'api.v1.admin.admin-users.permission-catalog', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.admin_user.manage', 'prd.admin_role.manage']],
|
||||
['code' => 'admin.admin-users.permissions.sync', 'module_code' => 'system', 'name' => '管理员权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}/permissions', 'route_name' => 'api.v1.admin.admin-users.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['code' => 'admin.admin-users.roles.sync', 'module_code' => 'system', 'name' => '管理员角色同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}/roles', 'route_name' => 'api.v1.admin.admin-users.roles.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['code' => 'admin.admin-roles.index', 'module_code' => 'system', 'name' => '角色列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-roles', 'route_name' => 'api.v1.admin.admin-roles.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
['code' => 'admin.admin-roles.store', 'module_code' => 'system', 'name' => '创建角色', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/admin-roles', 'route_name' => 'api.v1.admin.admin-roles.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
@@ -473,6 +472,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.settlement-batches.approve', 'module_code' => 'settlement', 'name' => '审核结算批次', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-batches/{batch}/approve', 'route_name' => 'api.v1.admin.settlement-batches.approve', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.payout.review']],
|
||||
['code' => 'admin.settlement-batches.reject', 'module_code' => 'settlement', 'name' => '驳回结算批次', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-batches/{batch}/reject', 'route_name' => 'api.v1.admin.settlement-batches.reject', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.payout.review']],
|
||||
['code' => 'admin.settlement-batches.payout', 'module_code' => 'settlement', 'name' => '执行派彩', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-batches/{batch}/payout', 'route_name' => 'api.v1.admin.settlement-batches.payout', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.payout.manage']],
|
||||
['code' => 'admin.settlement-batches.adjustments.store', 'module_code' => 'settlement', 'name' => '结算补差调账', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/settlement-batches/{batch}/adjustments', 'route_name' => 'api.v1.admin.settlement-batches.adjustments.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.payout.manage']],
|
||||
|
||||
['code' => 'admin.jackpot.pools.index', 'module_code' => 'jackpot', 'name' => '奖池列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/jackpot/pools', 'route_name' => 'api.v1.admin.jackpot.pools.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['code' => 'admin.jackpot.payout-logs.index', 'module_code' => 'jackpot', 'name' => '奖池派彩日志', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/jackpot/payout-logs', 'route_name' => 'api.v1.admin.jackpot.payout-logs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
|
||||
61
app/Support/AdminPermissionInheritance.php
Normal file
61
app/Support/AdminPermissionInheritance.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
final class AdminPermissionInheritance
|
||||
{
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private const IMPLIED_BY_SLUG = [
|
||||
'prd.agent.manage' => ['prd.agent.view'],
|
||||
'prd.agent.role.manage' => ['prd.agent.role.view'],
|
||||
'prd.agent.user.manage' => ['prd.agent.user.view'],
|
||||
'prd.integration.manage' => ['prd.integration.view'],
|
||||
'prd.wallet_reconcile.manage' => ['prd.wallet_reconcile.view'],
|
||||
'prd.draw_result.manage' => ['prd.draw_result.view'],
|
||||
'prd.odds.manage' => ['prd.odds.view'],
|
||||
'prd.risk_cap.manage' => ['prd.risk_cap.view'],
|
||||
'prd.rebate.manage' => ['prd.rebate.view'],
|
||||
'prd.jackpot.manage' => ['prd.jackpot.view'],
|
||||
'prd.payout.manage' => ['prd.payout.view'],
|
||||
'prd.payout.review' => ['prd.payout.view'],
|
||||
'prd.report.export' => ['prd.report.view'],
|
||||
'prd.risk.manage' => ['prd.risk.view'],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param list<string> $permissionSlugs
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function expand(array $permissionSlugs): array
|
||||
{
|
||||
$expanded = [];
|
||||
|
||||
foreach ($permissionSlugs as $slug) {
|
||||
if (! is_string($slug) || $slug === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::appendWithImplications($expanded, $slug);
|
||||
}
|
||||
|
||||
return array_values(array_keys($expanded));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, true> $accumulator
|
||||
*/
|
||||
private static function appendWithImplications(array &$accumulator, string $slug): void
|
||||
{
|
||||
if (isset($accumulator[$slug])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$accumulator[$slug] = true;
|
||||
|
||||
foreach (self::IMPLIED_BY_SLUG[$slug] ?? [] as $implied) {
|
||||
self::appendWithImplications($accumulator, $implied);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
app/Support/AdminScopeContext.php
Normal file
53
app/Support/AdminScopeContext.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\AgentNode;
|
||||
|
||||
final class AdminScopeContext
|
||||
{
|
||||
public function __construct(
|
||||
public readonly AdminUser $admin,
|
||||
public readonly ?string $requestedSiteCode = null,
|
||||
public readonly ?int $requestedAgentNodeId = null,
|
||||
) {}
|
||||
|
||||
public function isSuperAdmin(): bool
|
||||
{
|
||||
return $this->admin->isSuperAdmin();
|
||||
}
|
||||
|
||||
public function actorAgentNode(): ?AgentNode
|
||||
{
|
||||
return $this->admin->primaryAgentNode();
|
||||
}
|
||||
|
||||
public function actorAgentNodeId(): ?int
|
||||
{
|
||||
$node = $this->actorAgentNode();
|
||||
|
||||
return $node !== null ? (int) $node->id : null;
|
||||
}
|
||||
|
||||
public function adminSiteId(): ?int
|
||||
{
|
||||
$node = $this->actorAgentNode();
|
||||
|
||||
return $node !== null ? (int) $node->admin_site_id : null;
|
||||
}
|
||||
|
||||
public function effectiveRequestedAgentNodeId(): ?int
|
||||
{
|
||||
return $this->requestedAgentNodeId !== null && $this->requestedAgentNodeId > 0
|
||||
? $this->requestedAgentNodeId
|
||||
: null;
|
||||
}
|
||||
|
||||
public function effectiveRequestedSiteCode(): ?string
|
||||
{
|
||||
$siteCode = is_string($this->requestedSiteCode) ? trim($this->requestedSiteCode) : '';
|
||||
|
||||
return $siteCode !== '' ? $siteCode : null;
|
||||
}
|
||||
}
|
||||
42
app/Support/AdminScopeContextResolver.php
Normal file
42
app/Support/AdminScopeContextResolver.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
final class AdminScopeContextResolver
|
||||
{
|
||||
public static function fromRequest(
|
||||
Request $request,
|
||||
AdminUser $admin,
|
||||
string $siteParam = 'site_code',
|
||||
string $agentParam = 'agent_node_id',
|
||||
): AdminScopeContext {
|
||||
$siteCode = $request->query($siteParam);
|
||||
$agentNodeId = $request->integer($agentParam) ?: null;
|
||||
|
||||
return self::fromValues(
|
||||
$admin,
|
||||
is_string($siteCode) ? $siteCode : null,
|
||||
$agentNodeId,
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromValues(
|
||||
AdminUser $admin,
|
||||
?string $requestedSiteCode = null,
|
||||
?int $requestedAgentNodeId = null,
|
||||
): AdminScopeContext {
|
||||
$siteCode = is_string($requestedSiteCode) ? trim($requestedSiteCode) : '';
|
||||
$agentNodeId = $requestedAgentNodeId !== null && $requestedAgentNodeId > 0
|
||||
? $requestedAgentNodeId
|
||||
: null;
|
||||
|
||||
return new AdminScopeContext(
|
||||
admin: $admin,
|
||||
requestedSiteCode: $siteCode !== '' ? $siteCode : null,
|
||||
requestedAgentNodeId: $agentNodeId,
|
||||
);
|
||||
}
|
||||
}
|
||||
115
app/Support/AdminScopePolicy.php
Normal file
115
app/Support/AdminScopePolicy.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\TransferOrder;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* 统一后台数据范围策略:站点优先 + 代理子树收敛。
|
||||
*/
|
||||
final class AdminScopePolicy
|
||||
{
|
||||
public static function resolveContext(
|
||||
Request $request,
|
||||
AdminUser $admin,
|
||||
string $siteParam = 'site_code',
|
||||
string $agentParam = 'agent_node_id',
|
||||
): AdminScopeContext {
|
||||
return AdminScopeContextResolver::fromRequest($request, $admin, $siteParam, $agentParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EloquentBuilder<mixed> $query
|
||||
*/
|
||||
public static function applyViaPlayer(
|
||||
EloquentBuilder $query,
|
||||
AdminUser|AdminScopeContext $scope,
|
||||
string $relation = 'player',
|
||||
): void {
|
||||
$context = self::normalizeContext($scope);
|
||||
AdminSiteScope::applyViaPlayerRelation($query, $context->admin, $relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EloquentBuilder<\App\Models\Player> $query
|
||||
*/
|
||||
public static function applyPlayerFilters(EloquentBuilder $query, AdminScopeContext $context): void
|
||||
{
|
||||
AdminSiteScope::applyPlayerFilters(
|
||||
$query,
|
||||
$context->admin,
|
||||
$context->effectiveRequestedSiteCode(),
|
||||
$context->effectiveRequestedAgentNodeId(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EloquentBuilder<mixed> $query
|
||||
*/
|
||||
public static function applyViaPlayerRelationWithContext(
|
||||
EloquentBuilder $query,
|
||||
AdminScopeContext $context,
|
||||
string $relation = 'player',
|
||||
): void {
|
||||
AdminSiteScope::applyViaPlayerRelationWithSiteCode(
|
||||
$query,
|
||||
$context->admin,
|
||||
$context->effectiveRequestedSiteCode(),
|
||||
$relation,
|
||||
$context->effectiveRequestedAgentNodeId(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryBuilder<mixed> $query
|
||||
*/
|
||||
public static function applyPlayersAlias(
|
||||
QueryBuilder $query,
|
||||
AdminUser|AdminScopeContext $scope,
|
||||
string $alias = 'p',
|
||||
): void {
|
||||
$context = self::normalizeContext($scope);
|
||||
AdminDataScope::applyToPlayersAlias(
|
||||
$query,
|
||||
$context->admin,
|
||||
$alias,
|
||||
$context->effectiveRequestedAgentNodeId(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryBuilder<mixed> $query
|
||||
*/
|
||||
public static function applyTicketOrdersViaPlayer(
|
||||
QueryBuilder $query,
|
||||
AdminUser|AdminScopeContext $scope,
|
||||
string $orderAlias = 'o',
|
||||
): void {
|
||||
$context = self::normalizeContext($scope);
|
||||
AdminDataScope::applyToTicketOrdersViaPlayer($query, $context->admin, $orderAlias);
|
||||
}
|
||||
|
||||
public static function transferOrderAccessible(AdminUser|AdminScopeContext $scope, TransferOrder $order): bool
|
||||
{
|
||||
$context = self::normalizeContext($scope);
|
||||
$player = $order->player;
|
||||
if ($player === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AdminSiteScope::playerAccessible($context->admin, $player);
|
||||
}
|
||||
|
||||
private static function normalizeContext(AdminUser|AdminScopeContext $scope): AdminScopeContext
|
||||
{
|
||||
if ($scope instanceof AdminScopeContext) {
|
||||
return $scope;
|
||||
}
|
||||
|
||||
return AdminScopeContextResolver::fromValues($scope);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ final class AgentRoleAuthorization
|
||||
return false;
|
||||
}
|
||||
|
||||
return $admin->isSuperAdmin() || $admin->hasAdminPermission('prd.agent.role.manage');
|
||||
return $admin->isSuperAdmin() || $admin->hasPermissionCode('agent.node.manage');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
227
app/Support/AuditLogApiPresenter.php
Normal file
227
app/Support/AuditLogApiPresenter.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* 审计日志列表展示:模块 / 动作 / 目标中文标签(API 资源名 + 业务码映射)。
|
||||
*/
|
||||
final class AuditLogApiPresenter
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private const MODULE_LABELS = [
|
||||
'agent' => '代理',
|
||||
'system' => '系统',
|
||||
'settings' => '系统设置',
|
||||
'integration' => '对接站点',
|
||||
'player_manage' => '玩家管理',
|
||||
'risk_cap' => '风险池',
|
||||
'odds' => '赔率',
|
||||
'play_config' => '玩法配置',
|
||||
'settlement' => '结算',
|
||||
'report_jobs' => '报表任务',
|
||||
'reconcile_jobs' => '对账任务',
|
||||
'draw' => '开奖',
|
||||
'wallet' => '钱包',
|
||||
'reconcile' => '对账',
|
||||
'job' => '任务',
|
||||
'bootstrap' => '系统',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
private const ACTION_LABELS = [
|
||||
'agent_role.sync_permissions' => '同步代理角色权限',
|
||||
'admin_role.sync_permissions' => '同步平台角色权限',
|
||||
'admin_role.create' => '创建平台角色',
|
||||
'admin_role.update' => '更新平台角色',
|
||||
'admin_role.delete' => '删除平台角色',
|
||||
'agent_role.create' => '创建代理角色',
|
||||
'agent_role.update' => '更新代理角色',
|
||||
'agent_role.destroy' => '删除代理角色',
|
||||
'agent_admin_user.create' => '创建代理账号',
|
||||
'agent_admin_user.sync_roles' => '同步代理账号角色',
|
||||
'agent_node.create' => '创建代理节点',
|
||||
'agent_node.update' => '更新代理节点',
|
||||
'agent_node.destroy' => '删除代理节点',
|
||||
'agent.delegation.sync' => '同步代理授权',
|
||||
'admin_user.create' => '创建管理员',
|
||||
'admin_user.update' => '更新管理员',
|
||||
'admin_user.delete' => '删除管理员',
|
||||
'sync_permissions' => '同步权限',
|
||||
'batch_update' => '批量更新设置',
|
||||
'payout_adjustment' => '派彩调整',
|
||||
'rotate_secrets' => '轮换密钥',
|
||||
'toggle_active' => '切换启用状态',
|
||||
'enqueue' => '提交报表任务',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
private const TARGET_TYPE_LABELS = [
|
||||
'admin_role' => '角色',
|
||||
'admin_user' => '管理员',
|
||||
'agent_node' => '代理节点',
|
||||
'admin_site' => '对接站点',
|
||||
'player' => '玩家',
|
||||
'lottery_settings' => '系统设置',
|
||||
'lottery_setting' => '设置项',
|
||||
'risk_cap_version' => '风险池版本',
|
||||
'odds_version' => '赔率版本',
|
||||
'play_config_item' => '玩法',
|
||||
'play_config_version' => '玩法配置版本',
|
||||
'settlement_batch_adjustment' => '结算调整单',
|
||||
'report_job' => '报表任务',
|
||||
'reconcile_job' => '对账任务',
|
||||
'transfer_no' => '转账单',
|
||||
'draw' => '期次',
|
||||
'batch' => '批次',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
private const VERB_LABELS = [
|
||||
'sync' => '同步',
|
||||
'store' => '创建',
|
||||
'update' => '更新',
|
||||
'destroy' => '删除',
|
||||
'delete' => '删除',
|
||||
'create' => '创建',
|
||||
'publish' => '发布',
|
||||
'reopen' => '重新开奖',
|
||||
'freeze' => '冻结',
|
||||
'unfreeze' => '解冻',
|
||||
'run' => '执行',
|
||||
'transfer' => '转账',
|
||||
'start' => '开始',
|
||||
'test' => '测试',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param LengthAwarePaginator<AuditLog> $paginator
|
||||
* @return array{items: list<array<string, mixed>>, meta: array{current_page: int, per_page: int, total: int, last_page: int}}
|
||||
*/
|
||||
public static function listPayload(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
$items = collect($paginator->items());
|
||||
$resourceNames = self::loadResourceNames($items);
|
||||
|
||||
return AdminApiList::payload(
|
||||
$paginator,
|
||||
fn (AuditLog $r) => self::row($r, $resourceNames),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $resourceNames
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function row(AuditLog $r, array $resourceNames = []): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $r->id,
|
||||
'operator_type' => $r->operator_type,
|
||||
'operator_id' => (int) $r->operator_id,
|
||||
'module_code' => $r->module_code,
|
||||
'action_code' => $r->action_code,
|
||||
'target_type' => $r->target_type,
|
||||
'target_id' => $r->target_id,
|
||||
'module_label' => self::moduleLabel($r->module_code),
|
||||
'action_label' => self::actionLabel($r, $resourceNames),
|
||||
'target_label' => self::targetLabel($r, $resourceNames),
|
||||
'before_json' => $r->before_json,
|
||||
'after_json' => $r->after_json,
|
||||
'ip' => $r->ip,
|
||||
'user_agent' => $r->user_agent,
|
||||
'created_at' => $r->created_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, AuditLog> $items
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private static function loadResourceNames(Collection $items): array
|
||||
{
|
||||
$codes = $items
|
||||
->pluck('target_type')
|
||||
->filter(fn (?string $type) => self::isApiResourceCode($type))
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if ($codes === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array<string, string> */
|
||||
return DB::table('admin_api_resources')
|
||||
->whereIn('code', $codes)
|
||||
->pluck('name', 'code')
|
||||
->all();
|
||||
}
|
||||
|
||||
private static function moduleLabel(?string $code): string
|
||||
{
|
||||
if ($code === null || $code === '') {
|
||||
return '—';
|
||||
}
|
||||
|
||||
return self::MODULE_LABELS[$code] ?? $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $resourceNames
|
||||
*/
|
||||
private static function actionLabel(AuditLog $r, array $resourceNames): string
|
||||
{
|
||||
$action = (string) ($r->action_code ?? '');
|
||||
if ($action === '') {
|
||||
return '—';
|
||||
}
|
||||
|
||||
if (isset(self::ACTION_LABELS[$action])) {
|
||||
return self::ACTION_LABELS[$action];
|
||||
}
|
||||
|
||||
$targetType = (string) ($r->target_type ?? '');
|
||||
if (self::isApiResourceCode($targetType) && isset($resourceNames[$targetType])) {
|
||||
return $resourceNames[$targetType];
|
||||
}
|
||||
|
||||
if (isset(self::VERB_LABELS[$action])) {
|
||||
return self::VERB_LABELS[$action];
|
||||
}
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $resourceNames
|
||||
*/
|
||||
private static function targetLabel(AuditLog $r, array $resourceNames): string
|
||||
{
|
||||
$type = (string) ($r->target_type ?? '');
|
||||
$id = trim((string) ($r->target_id ?? ''));
|
||||
|
||||
if ($type === '') {
|
||||
return $id !== '' ? '#'.$id : '—';
|
||||
}
|
||||
|
||||
if (self::isApiResourceCode($type)) {
|
||||
$name = $resourceNames[$type] ?? $type;
|
||||
|
||||
return $id !== '' ? sprintf('%s #%s', $name, $id) : $name;
|
||||
}
|
||||
|
||||
$typeLabel = self::TARGET_TYPE_LABELS[$type] ?? $type;
|
||||
|
||||
return $id !== '' ? sprintf('%s #%s', $typeLabel, $id) : $typeLabel;
|
||||
}
|
||||
|
||||
private static function isApiResourceCode(?string $code): bool
|
||||
{
|
||||
return is_string($code) && str_starts_with($code, 'admin.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user