feat(admin): 增加玩家密码管理与角色边界
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Player;
|
||||
|
||||
use App\Models\Player;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\AdminSiteScope;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Player\PlayerPasswordService;
|
||||
use App\Http\Requests\Admin\AdminPlayerPasswordResetRequest;
|
||||
|
||||
/** PUT /api/v1/admin/players/{player}/password */
|
||||
final class AdminPlayerPasswordResetController extends Controller
|
||||
{
|
||||
public function __invoke(
|
||||
AdminPlayerPasswordResetRequest $request,
|
||||
Player $player,
|
||||
PlayerPasswordService $passwords,
|
||||
): JsonResponse {
|
||||
$admin = $request->lotteryAdmin();
|
||||
abort_if($admin === null, 401);
|
||||
|
||||
if ($denied = AdminSiteScope::denyUnlessPlayerAccessible($admin, $player)) {
|
||||
return $denied;
|
||||
}
|
||||
|
||||
$updated = $passwords->reset($player, (string) $request->validated('password'));
|
||||
|
||||
return ApiResponse::success([
|
||||
'password_reset' => true,
|
||||
'player_id' => (int) $updated->id,
|
||||
], request: $request);
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,12 @@ namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleUserCounts;
|
||||
use App\Support\PlatformSystemRoles;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
|
||||
/** GET /api/v1/admin/admin-user-permission-catalog */
|
||||
final class AdminPermissionCatalogController extends Controller
|
||||
@@ -61,12 +62,26 @@ final class AdminPermissionCatalogController extends Controller
|
||||
->where('scope_type', AdminRole::SCOPE_SYSTEM)
|
||||
->orderBy('slug')
|
||||
->get(['id', 'slug', 'name']);
|
||||
$userCounts = AdminRoleUserCounts::forRoleIds($roles->pluck('id'));
|
||||
$presentedRoles = $roles->map(
|
||||
static fn (AdminRole $role): array => AdminRoleApiPresenter::item(
|
||||
$role,
|
||||
$userCounts[(int) $role->id] ?? null,
|
||||
)
|
||||
)->values();
|
||||
|
||||
return ApiResponse::success([
|
||||
'permissions' => $permissions,
|
||||
'permission_menu_groups' => $permissionMenuGroups,
|
||||
'navigation' => AdminAuthorizationRegistry::navigationItems(),
|
||||
'roles' => $roles->map(static fn (AdminRole $role): array => AdminRoleApiPresenter::item($role))->values()->all(),
|
||||
'roles' => $presentedRoles->all(),
|
||||
'assignable_roles' => $presentedRoles
|
||||
->reject(static fn (array $role): bool => in_array($role['slug'], [
|
||||
PlatformSystemRoles::SLUG_AGENT,
|
||||
PlatformSystemRoles::SLUG_SUPER_ADMIN,
|
||||
], true))
|
||||
->values()
|
||||
->all(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleUserCounts;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
|
||||
final class AdminRoleIndexController extends Controller
|
||||
{
|
||||
public function __invoke(): JsonResponse
|
||||
@@ -16,9 +18,15 @@ final class AdminRoleIndexController extends Controller
|
||||
->orderBy('sort_order')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
$userCounts = AdminRoleUserCounts::forRoleIds($roles->pluck('id'));
|
||||
|
||||
return ApiResponse::success([
|
||||
'items' => $roles->map(static fn (AdminRole $role): array => AdminRoleApiPresenter::item($role))->values()->all(),
|
||||
'items' => $roles->map(
|
||||
static fn (AdminRole $role): array => AdminRoleApiPresenter::item(
|
||||
$role,
|
||||
$userCounts[(int) $role->id] ?? null,
|
||||
)
|
||||
)->values()->all(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Support\AdminApiList;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminUserApiPresenter;
|
||||
|
||||
@@ -17,6 +17,7 @@ final class AdminUserIndexController extends Controller
|
||||
{
|
||||
$p = AdminApiList::readPaging($request);
|
||||
$keyword = trim((string) $request->query('keyword', ''));
|
||||
$roleSlug = trim((string) $request->query('role_slug', ''));
|
||||
|
||||
$q = AdminUser::query()
|
||||
->with(['roles'])
|
||||
@@ -35,6 +36,12 @@ final class AdminUserIndexController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
if ($roleSlug !== '') {
|
||||
$q->whereHas('roles', static function ($roles) use ($roleSlug): void {
|
||||
$roles->where('admin_roles.slug', $roleSlug);
|
||||
});
|
||||
}
|
||||
|
||||
$paginator = $q->paginate($p['perPage'], ['*'], 'page', $p['page']);
|
||||
|
||||
return AdminApiList::json($paginator, fn (AdminUser $user): array => AdminUserApiPresenter::listItem($user));
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Player;
|
||||
|
||||
use App\Support\ApiResponse;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Player\PlayerPasswordService;
|
||||
use App\Http\Requests\Player\PlayerPasswordUpdateRequest;
|
||||
|
||||
/** PUT /api/v1/player/password — 彩票端账号自助修改密码 */
|
||||
final class PlayerPasswordUpdateController extends Controller
|
||||
{
|
||||
public function __invoke(
|
||||
PlayerPasswordUpdateRequest $request,
|
||||
PlayerPasswordService $passwords,
|
||||
): JsonResponse {
|
||||
$player = $request->lotteryPlayer();
|
||||
abort_if($player === null, 500, 'lottery_player missing');
|
||||
|
||||
$updated = $passwords->change(
|
||||
$player,
|
||||
(string) $request->validated('current_password'),
|
||||
(string) $request->validated('password'),
|
||||
);
|
||||
|
||||
AuditLogger::recordForPlayer(
|
||||
$updated,
|
||||
$request,
|
||||
'player_account',
|
||||
'change_password',
|
||||
'player',
|
||||
(string) $updated->id,
|
||||
null,
|
||||
['native_token_version' => (int) $updated->native_token_version],
|
||||
);
|
||||
|
||||
return ApiResponse::success(['password_changed' => true], request: $request);
|
||||
}
|
||||
}
|
||||
23
app/Http/Requests/Admin/AdminPlayerPasswordResetRequest.php
Normal file
23
app/Http/Requests/Admin/AdminPlayerPasswordResetRequest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
use App\Http\Requests\Admin\Concerns\AdminAccountFieldRules;
|
||||
|
||||
final class AdminPlayerPasswordResetRequest extends ApiFormRequest
|
||||
{
|
||||
use AdminAccountFieldRules;
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => [...$this->nativePlayerPasswordRules(required: true), 'confirmed'],
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Http/Requests/Player/PlayerPasswordUpdateRequest.php
Normal file
24
app/Http/Requests/Player/PlayerPasswordUpdateRequest.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Player;
|
||||
|
||||
use App\Http\Requests\ApiFormRequest;
|
||||
use App\Http\Requests\Admin\Concerns\AdminAccountFieldRules;
|
||||
|
||||
final class PlayerPasswordUpdateRequest extends ApiFormRequest
|
||||
{
|
||||
use AdminAccountFieldRules;
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'current_password' => ['required', 'string', 'max:128'],
|
||||
'password' => [...$this->nativePlayerPasswordRules(required: true), 'confirmed'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -251,6 +251,11 @@ final class AdminUser extends Authenticatable
|
||||
{
|
||||
$slugs = array_values(array_unique($slugs));
|
||||
\App\Support\SuperAdminAccount::assertNotSiteRoleAssignment($slugs);
|
||||
if (in_array(\App\Support\PlatformSystemRoles::SLUG_AGENT, $slugs, true)) {
|
||||
throw ValidationException::withMessages([
|
||||
'role_slugs' => [trans('admin.agent_role_not_assignable_to_platform_account')],
|
||||
]);
|
||||
}
|
||||
|
||||
$roleIds = DB::table('admin_roles')
|
||||
->where('scope_type', AdminRole::SCOPE_SYSTEM)
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Models;
|
||||
|
||||
use App\Support\PlayerAuthSource;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* 主站玩家在本地映射账号(表 players),与 SSO JWT 中 site_code + site_player_id 对应。
|
||||
@@ -27,6 +27,7 @@ final class Player extends Model
|
||||
'last_login_at',
|
||||
'login_failed_count',
|
||||
'login_locked_until',
|
||||
'native_token_version',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -40,6 +41,7 @@ final class Player extends Model
|
||||
'last_login_at' => 'datetime',
|
||||
'login_failed_count' => 'integer',
|
||||
'login_locked_until' => 'datetime',
|
||||
'native_token_version' => 'integer',
|
||||
'risk_tags' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Services\Player;
|
||||
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Models\Player;
|
||||
use App\Support\PlayerAuthSource;
|
||||
use Firebase\JWT\JWT;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\Player;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\PlayerAuthSource;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Exceptions\PlayerAuthenticationException;
|
||||
|
||||
@@ -103,6 +102,7 @@ final class PlayerNativeAuthService
|
||||
$payload = [
|
||||
$playerIdKey => (int) $player->id,
|
||||
$authKey => PlayerAuthSource::LOTTERY_NATIVE,
|
||||
'token_version' => (int) ($player->native_token_version ?? 0),
|
||||
'site_code' => (string) $player->site_code,
|
||||
'iat' => $now,
|
||||
'exp' => $now + $ttl,
|
||||
|
||||
64
app/Services/Player/PlayerPasswordService.php
Normal file
64
app/Services/Player/PlayerPasswordService.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Player;
|
||||
|
||||
use App\Models\Player;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
final class PlayerPasswordService
|
||||
{
|
||||
public function change(Player $player, string $currentPassword, string $newPassword): Player
|
||||
{
|
||||
return DB::transaction(function () use ($player, $currentPassword, $newPassword): Player {
|
||||
$locked = Player::query()->lockForUpdate()->findOrFail($player->id);
|
||||
$this->assertPasswordManagedLocally($locked);
|
||||
|
||||
if (! is_string($locked->password_hash) || ! Hash::check($currentPassword, $locked->password_hash)) {
|
||||
throw ValidationException::withMessages([
|
||||
'current_password' => ['current_password_invalid'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (Hash::check($newPassword, $locked->password_hash)) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => ['new_password_must_differ'],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->persist($locked, $newPassword);
|
||||
});
|
||||
}
|
||||
|
||||
public function reset(Player $player, string $newPassword): Player
|
||||
{
|
||||
return DB::transaction(function () use ($player, $newPassword): Player {
|
||||
$locked = Player::query()->lockForUpdate()->findOrFail($player->id);
|
||||
$this->assertPasswordManagedLocally($locked);
|
||||
|
||||
return $this->persist($locked, $newPassword);
|
||||
});
|
||||
}
|
||||
|
||||
private function assertPasswordManagedLocally(Player $player): void
|
||||
{
|
||||
if (! $player->isLotteryNative()) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => ['native_password_unavailable'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function persist(Player $player, string $newPassword): Player
|
||||
{
|
||||
$player->forceFill([
|
||||
'password_hash' => Hash::make($newPassword),
|
||||
'native_token_version' => (int) $player->native_token_version + 1,
|
||||
'login_failed_count' => 0,
|
||||
'login_locked_until' => null,
|
||||
])->save();
|
||||
|
||||
return $player->refresh();
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ use App\Models\Player;
|
||||
use App\Lottery\ErrorCode;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Support\PlayerAuthSource;
|
||||
use App\Support\PlayerAutoRegistrationDefaults;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use App\Support\PlayerTokenAesUnwrap;
|
||||
use Illuminate\Database\QueryException;
|
||||
use App\Support\PlayerAutoRegistrationDefaults;
|
||||
use App\Exceptions\PlayerAuthenticationException;
|
||||
use App\Services\Integration\PartnerSiteConfigResolver;
|
||||
|
||||
@@ -192,6 +192,11 @@ final class PlayerTokenResolver
|
||||
throw new PlayerAuthenticationException('玩家不存在', ErrorCode::PlayerNotRegistered->value);
|
||||
}
|
||||
|
||||
$tokenVersion = (int) data_get($claims, 'token_version', 0);
|
||||
if ($tokenVersion !== (int) ($player->native_token_version ?? 0)) {
|
||||
throw new PlayerAuthenticationException('Token 已因密码变更失效', ErrorCode::PlayerTokenInvalid->value);
|
||||
}
|
||||
|
||||
$player->forceFill(['last_login_at' => now()])->save();
|
||||
|
||||
return $player->refresh();
|
||||
@@ -288,9 +293,6 @@ final class PlayerTokenResolver
|
||||
return is_string($decoded) ? $decoded : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $claims
|
||||
*/
|
||||
private function assertNativeJwtTemporalPolicy(object $claims): void
|
||||
{
|
||||
if (! isset($claims->exp) || ! is_numeric($claims->exp)) {
|
||||
|
||||
@@ -550,6 +550,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.players.store', 'module_code' => 'player_service', 'name' => '创建玩家', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/players', 'route_name' => 'api.v1.admin.players.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.manage']],
|
||||
['code' => 'admin.players.show', 'module_code' => 'player_service', 'name' => '玩家详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}', 'route_name' => 'api.v1.admin.players.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.players.view']],
|
||||
['code' => 'admin.players.update', 'module_code' => 'player_service', 'name' => '更新玩家', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/players/{player}', 'route_name' => 'api.v1.admin.players.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.manage']],
|
||||
['code' => 'admin.players.password.reset', 'module_code' => 'player_service', 'name' => '重置玩家密码', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/players/{player}/password', 'route_name' => 'api.v1.admin.players.password.reset', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.manage']],
|
||||
['code' => 'admin.players.destroy', 'module_code' => 'player_service', 'name' => '删除玩家', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/players/{player}', 'route_name' => 'api.v1.admin.players.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.manage']],
|
||||
['code' => 'admin.players.freeze', 'module_code' => 'player_service', 'name' => '冻结玩家', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/players/{player}/freeze', 'route_name' => 'api.v1.admin.players.freeze', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.freeze']],
|
||||
['code' => 'admin.players.unfreeze', 'module_code' => 'player_service', 'name' => '解冻玩家', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/players/{player}/unfreeze', 'route_name' => 'api.v1.admin.players.unfreeze', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.players.freeze']],
|
||||
|
||||
@@ -7,8 +7,25 @@ use App\Models\AdminRole;
|
||||
final class AdminRoleApiPresenter
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public static function item(AdminRole $role): array
|
||||
public static function item(AdminRole $role, ?array $userCounts = null): array
|
||||
{
|
||||
if ($userCounts !== null) {
|
||||
$counts = $userCounts;
|
||||
} elseif (($role->scope_type ?? AdminRole::SCOPE_SYSTEM) === AdminRole::SCOPE_SYSTEM) {
|
||||
$counts = AdminRoleUserCounts::forRoleIds([$role->id])[(int) $role->id] ?? [
|
||||
'user_count' => 0,
|
||||
'platform_user_count' => 0,
|
||||
'agent_user_count' => 0,
|
||||
];
|
||||
} else {
|
||||
$assignedCount = $role->assignedUserCount();
|
||||
$counts = [
|
||||
'user_count' => $assignedCount,
|
||||
'platform_user_count' => 0,
|
||||
'agent_user_count' => $assignedCount,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) $role->id,
|
||||
'slug' => $role->slug,
|
||||
@@ -22,7 +39,9 @@ final class AdminRoleApiPresenter
|
||||
'delegated_from_role_id' => $role->delegated_from_role_id !== null ? (int) $role->delegated_from_role_id : null,
|
||||
'is_read_only_template' => $role->isReadOnlyTemplate(),
|
||||
'permission_slugs' => $role->legacyPermissionSlugs(),
|
||||
'user_count' => $role->assignedUserCount(),
|
||||
'user_count' => $counts['user_count'],
|
||||
'platform_user_count' => $counts['platform_user_count'],
|
||||
'agent_user_count' => $counts['agent_user_count'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
46
app/Support/AdminRoleUserCounts.php
Normal file
46
app/Support/AdminRoleUserCounts.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class AdminRoleUserCounts
|
||||
{
|
||||
/**
|
||||
* @param iterable<int> $roleIds
|
||||
* @return array<int, array{user_count: int, platform_user_count: int, agent_user_count: int}>
|
||||
*/
|
||||
public static function forRoleIds(iterable $roleIds): array
|
||||
{
|
||||
$ids = collect($roleIds)
|
||||
->map(static fn ($id): int => (int) $id)
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($ids->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DB::table('admin_user_site_roles as usr')
|
||||
->leftJoin('admin_user_agents as uag', 'uag.admin_user_id', '=', 'usr.admin_user_id')
|
||||
->whereIn('usr.role_id', $ids->all())
|
||||
->groupBy('usr.role_id')
|
||||
->selectRaw(
|
||||
'usr.role_id, '
|
||||
.'COUNT(DISTINCT CASE WHEN uag.admin_user_id IS NULL THEN usr.admin_user_id END) AS platform_user_count, '
|
||||
.'COUNT(DISTINCT CASE WHEN uag.admin_user_id IS NOT NULL THEN usr.admin_user_id END) AS agent_user_count'
|
||||
)
|
||||
->get()
|
||||
->mapWithKeys(static function (object $row): array {
|
||||
$platformCount = (int) $row->platform_user_count;
|
||||
$agentCount = (int) $row->agent_user_count;
|
||||
|
||||
return [(int) $row->role_id => [
|
||||
'user_count' => $platformCount + $agentCount,
|
||||
'platform_user_count' => $platformCount,
|
||||
'agent_user_count' => $agentCount,
|
||||
]];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
}
|
||||
30
app/Support/InvalidPlatformAgentRoleCleanup.php
Normal file
30
app/Support/InvalidPlatformAgentRoleCleanup.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class InvalidPlatformAgentRoleCleanup
|
||||
{
|
||||
public static function run(): int
|
||||
{
|
||||
$agentRoleId = DB::table('admin_roles')
|
||||
->where('scope_type', AdminRole::SCOPE_SYSTEM)
|
||||
->where('slug', PlatformSystemRoles::SLUG_AGENT)
|
||||
->value('id');
|
||||
|
||||
if ($agentRoleId === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DB::table('admin_user_site_roles')
|
||||
->where('role_id', (int) $agentRoleId)
|
||||
->whereNotExists(static function ($query): void {
|
||||
$query->selectRaw('1')
|
||||
->from('admin_user_agents as cleanup_uag')
|
||||
->whereColumn('cleanup_uag.admin_user_id', 'admin_user_site_roles.admin_user_id');
|
||||
})
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user