feat(admin): 完善后台角色管理与权限同步,新增当前管理员信息接口
This commit is contained in:
@@ -6,7 +6,7 @@ use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
use App\Support\AdminAuthProfile;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@@ -69,19 +69,10 @@ final class LoginController extends Controller
|
||||
)->plainTextToken;
|
||||
|
||||
$admin->forceFill(['last_login_at' => now()])->save();
|
||||
$permissionSlugs = $admin->fresh()->adminPermissionSlugs();
|
||||
|
||||
return ApiResponse::success([
|
||||
'token' => $plainToken,
|
||||
'token_type' => 'Bearer',
|
||||
'admin' => [
|
||||
'id' => $admin->id,
|
||||
'username' => $admin->username,
|
||||
'nickname' => $admin->name,
|
||||
'email' => $admin->email,
|
||||
'permissions' => $permissionSlugs,
|
||||
'navigation' => AdminAuthorizationRegistry::visibleNavigationItems($permissionSlugs),
|
||||
],
|
||||
'admin' => AdminAuthProfile::fromAdmin($admin),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
23
app/Http/Controllers/Api/V1/Admin/Auth/MeController.php
Normal file
23
app/Http/Controllers/Api/V1/Admin/Auth/MeController.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\Auth;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminAuthProfile;
|
||||
|
||||
final class MeController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
{
|
||||
/** @var AdminUser $admin */
|
||||
$admin = $request->lotteryAdmin();
|
||||
|
||||
return ApiResponse::success([
|
||||
'admin' => AdminAuthProfile::fromAdmin($admin),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use App\Support\AdminAuthorizationRegistry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
|
||||
/** GET /api/v1/admin/admin-user-permission-catalog */
|
||||
final class AdminPermissionCatalogController extends Controller
|
||||
@@ -64,20 +65,7 @@ final class AdminPermissionCatalogController extends Controller
|
||||
'permissions' => $permissions,
|
||||
'permission_menu_groups' => $permissionMenuGroups,
|
||||
'navigation' => AdminAuthorizationRegistry::navigationItems(),
|
||||
'roles' => $roles->map(static function (AdminRole $role): array {
|
||||
$userCount = (int) DB::table('admin_user_site_roles')
|
||||
->where('role_id', $role->id)
|
||||
->distinct()
|
||||
->count('admin_user_id');
|
||||
|
||||
return [
|
||||
'id' => (int) $role->id,
|
||||
'slug' => $role->slug,
|
||||
'name' => $role->name,
|
||||
'permission_slugs' => $role->legacyPermissionSlugs(),
|
||||
'user_count' => $userCount,
|
||||
];
|
||||
})->values()->all(),
|
||||
'roles' => $roles->map(static fn (AdminRole $role): array => AdminRoleApiPresenter::item($role))->values()->all(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
|
||||
final class AdminRoleDestroyController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, AdminRole $admin_role): JsonResponse
|
||||
{
|
||||
if ($admin_role->slug === AdminRole::ROLE_SUPER_ADMIN) {
|
||||
return ApiResponse::error('不能删除超级管理员角色', ErrorCode::ValidationFailed->value, null, 422);
|
||||
}
|
||||
if ((bool) $admin_role->is_system) {
|
||||
return ApiResponse::error('系统内置角色不允许删除', ErrorCode::ValidationFailed->value, null, 422);
|
||||
}
|
||||
if ($admin_role->assignedUserCount() > 0) {
|
||||
return ApiResponse::error('该角色下仍有关联管理员,不能删除', ErrorCode::ValidationFailed->value, null, 422);
|
||||
}
|
||||
|
||||
$before = AdminRoleApiPresenter::item($admin_role);
|
||||
$id = (int) $admin_role->id;
|
||||
$admin_role->delete();
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$request->lotteryAdmin(),
|
||||
$request,
|
||||
'system',
|
||||
'admin_role.delete',
|
||||
'admin_role',
|
||||
(string) $id,
|
||||
$before,
|
||||
null,
|
||||
);
|
||||
|
||||
return ApiResponse::success(['deleted' => true, 'id' => $id]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
|
||||
final class AdminRoleIndexController extends Controller
|
||||
{
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
$roles = AdminRole::query()->orderBy('sort_order')->orderBy('id')->get();
|
||||
|
||||
return ApiResponse::success([
|
||||
'items' => $roles->map(static fn (AdminRole $role): array => AdminRoleApiPresenter::item($role))->values()->all(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
use App\Http\Requests\Admin\AdminRolePermissionSyncRequest;
|
||||
|
||||
final class AdminRolePermissionSyncController extends Controller
|
||||
{
|
||||
public function __invoke(AdminRolePermissionSyncRequest $request, AdminRole $admin_role): JsonResponse
|
||||
{
|
||||
$slugs = array_values(array_unique($request->validated('permission_slugs', [])));
|
||||
$before = AdminRoleApiPresenter::item($admin_role);
|
||||
|
||||
DB::transaction(function () use ($admin_role, $slugs): void {
|
||||
$admin_role->syncLegacyPermissionSlugs($slugs);
|
||||
});
|
||||
$admin_role->refresh();
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$request->lotteryAdmin(),
|
||||
$request,
|
||||
'system',
|
||||
'admin_role.sync_permissions',
|
||||
'admin_role',
|
||||
(string) $admin_role->id,
|
||||
$before,
|
||||
AdminRoleApiPresenter::item($admin_role),
|
||||
);
|
||||
|
||||
return ApiResponse::success(AdminRoleApiPresenter::item($admin_role));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
use App\Http\Requests\Admin\AdminRoleStoreRequest;
|
||||
|
||||
final class AdminRoleStoreController extends Controller
|
||||
{
|
||||
public function __invoke(AdminRoleStoreRequest $request): JsonResponse
|
||||
{
|
||||
$permissionSlugs = array_values(array_unique($request->validated('permission_slugs', [])));
|
||||
|
||||
$role = DB::transaction(function () use ($request, $permissionSlugs): AdminRole {
|
||||
$role = AdminRole::query()->create([
|
||||
'slug' => $request->validated('slug'),
|
||||
'code' => $request->validated('slug'),
|
||||
'name' => $request->validated('name'),
|
||||
'description' => $request->validated('description'),
|
||||
'status' => $request->validated('status', 1),
|
||||
'is_system' => false,
|
||||
'sort_order' => 0,
|
||||
]);
|
||||
$role->syncLegacyPermissionSlugs($permissionSlugs);
|
||||
|
||||
return $role->fresh();
|
||||
});
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$request->lotteryAdmin(),
|
||||
$request,
|
||||
'system',
|
||||
'admin_role.create',
|
||||
'admin_role',
|
||||
(string) $role->id,
|
||||
null,
|
||||
AdminRoleApiPresenter::item($role),
|
||||
);
|
||||
|
||||
return ApiResponse::success(AdminRoleApiPresenter::item($role));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
use App\Support\ApiResponse;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AdminRoleApiPresenter;
|
||||
use App\Http\Requests\Admin\AdminRoleUpdateRequest;
|
||||
|
||||
final class AdminRoleUpdateController extends Controller
|
||||
{
|
||||
public function __invoke(AdminRoleUpdateRequest $request, AdminRole $admin_role): JsonResponse
|
||||
{
|
||||
$before = AdminRoleApiPresenter::item($admin_role);
|
||||
|
||||
$payload = [];
|
||||
foreach (['slug', 'name', 'description', 'status'] as $field) {
|
||||
if ($request->has($field)) {
|
||||
$payload[$field] = $request->validated($field);
|
||||
}
|
||||
}
|
||||
if (isset($payload['slug'])) {
|
||||
$payload['code'] = $payload['slug'];
|
||||
}
|
||||
|
||||
$admin_role->fill($payload);
|
||||
$admin_role->save();
|
||||
$admin_role->refresh();
|
||||
|
||||
AuditLogger::recordForAdmin(
|
||||
$request->lotteryAdmin(),
|
||||
$request,
|
||||
'system',
|
||||
'admin_role.update',
|
||||
'admin_role',
|
||||
(string) $admin_role->id,
|
||||
$before,
|
||||
AdminRoleApiPresenter::item($admin_role),
|
||||
);
|
||||
|
||||
return ApiResponse::success(AdminRoleApiPresenter::item($admin_role));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Admin\User\Concerns;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use App\Support\ApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
trait EnsuresSuperAdminActor
|
||||
{
|
||||
protected function ensureSuperAdmin(Request $request): ?JsonResponse
|
||||
{
|
||||
/** @var AdminUser $actor */
|
||||
$actor = $request->lotteryAdmin();
|
||||
if (! $actor->isSuperAdmin()) {
|
||||
return ApiResponse::error(
|
||||
'仅超级管理员可管理角色',
|
||||
ErrorCode::AdminForbidden->value,
|
||||
null,
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,8 @@ final class TransferOrderListController extends Controller
|
||||
'amount_formatted' => CurrencyFormatter::fromMinor($amount),
|
||||
'idempotent_key' => $o->idempotent_key,
|
||||
'status' => $o->status,
|
||||
'can_reverse' => $o->status === 'pending_reconcile',
|
||||
'can_manually_process' => in_array($o->status, ['processing', 'failed', 'pending_reconcile'], true),
|
||||
'external_ref_no' => $o->external_ref_no,
|
||||
'external_request_payload' => $o->external_request_payload,
|
||||
'external_response_payload' => $o->external_response_payload,
|
||||
|
||||
21
app/Http/Requests/Admin/AdminRolePermissionSyncRequest.php
Normal file
21
app/Http/Requests/Admin/AdminRolePermissionSyncRequest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AdminRolePermissionSyncRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'permission_slugs' => ['required', 'array'],
|
||||
'permission_slugs.*' => ['string', 'max:128'],
|
||||
];
|
||||
}
|
||||
}
|
||||
25
app/Http/Requests/Admin/AdminRoleStoreRequest.php
Normal file
25
app/Http/Requests/Admin/AdminRoleStoreRequest.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AdminRoleStoreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'slug' => ['required', 'string', 'max:64', 'regex:/^[a-z0-9_\\-]+$/', 'unique:admin_roles,slug'],
|
||||
'name' => ['required', 'string', 'max:128'],
|
||||
'description' => ['nullable', 'string', 'max:65535'],
|
||||
'status' => ['sometimes', 'integer', 'in:0,1'],
|
||||
'permission_slugs' => ['sometimes', 'array'],
|
||||
'permission_slugs.*' => ['string', 'max:128'],
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Http/Requests/Admin/AdminRoleUpdateRequest.php
Normal file
26
app/Http/Requests/Admin/AdminRoleUpdateRequest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AdminRoleUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$roleId = $this->route('admin_role')?->id;
|
||||
|
||||
return [
|
||||
'slug' => ['sometimes', 'string', 'max:64', 'regex:/^[a-z0-9_\\-]+$/', Rule::unique('admin_roles', 'slug')->ignore($roleId)],
|
||||
'name' => ['sometimes', 'string', 'max:128'],
|
||||
'description' => ['nullable', 'string', 'max:65535'],
|
||||
'status' => ['sometimes', 'integer', 'in:0,1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
final class AdminRole extends Model
|
||||
{
|
||||
public const ROLE_SUPER_ADMIN = 'super_admin';
|
||||
|
||||
protected $table = 'admin_roles';
|
||||
|
||||
protected static function booted(): void
|
||||
@@ -59,6 +61,25 @@ final class AdminRole extends Model
|
||||
*/
|
||||
public function legacyPermissionSlugs(): array
|
||||
{
|
||||
if (DB::getSchemaBuilder()->hasTable('admin_role_legacy_permissions')) {
|
||||
$slugs = DB::table('admin_role_legacy_permissions')
|
||||
->where('role_id', $this->id)
|
||||
->pluck('permission_slug')
|
||||
->all();
|
||||
|
||||
$out = [];
|
||||
foreach ($slugs as $slug) {
|
||||
if (is_string($slug) && $slug !== '') {
|
||||
$out[$slug] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array_keys($out);
|
||||
sort($keys);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
$codes = DB::table('admin_role_menu_actions as rma')
|
||||
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
|
||||
->where('rma.role_id', $this->id)
|
||||
@@ -68,4 +89,56 @@ final class AdminRole extends Model
|
||||
|
||||
return AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes($codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $slugs
|
||||
*/
|
||||
public function syncLegacyPermissionSlugs(array $slugs): void
|
||||
{
|
||||
$legacySlugs = array_values(array_unique(array_filter(
|
||||
$slugs,
|
||||
static fn ($slug): bool => is_string($slug) && $slug !== '',
|
||||
)));
|
||||
|
||||
$codes = [];
|
||||
foreach ($legacySlugs as $slug) {
|
||||
$codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug));
|
||||
}
|
||||
$codes = array_values(array_unique($codes));
|
||||
|
||||
$ids = DB::table('admin_menu_actions')
|
||||
->whereIn('permission_code', $codes)
|
||||
->where('status', 1)
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
DB::table('admin_role_menu_actions')->where('role_id', $this->id)->delete();
|
||||
foreach ($ids as $mid) {
|
||||
DB::table('admin_role_menu_actions')->insert([
|
||||
'role_id' => $this->id,
|
||||
'menu_action_id' => (int) $mid,
|
||||
]);
|
||||
}
|
||||
|
||||
if (DB::getSchemaBuilder()->hasTable('admin_role_legacy_permissions')) {
|
||||
DB::table('admin_role_legacy_permissions')->where('role_id', $this->id)->delete();
|
||||
$now = now();
|
||||
foreach ($legacySlugs as $slug) {
|
||||
DB::table('admin_role_legacy_permissions')->insert([
|
||||
'role_id' => $this->id,
|
||||
'permission_slug' => $slug,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function assignedUserCount(): int
|
||||
{
|
||||
return (int) DB::table('admin_user_site_roles')
|
||||
->where('role_id', $this->id)
|
||||
->distinct()
|
||||
->count('admin_user_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,11 @@ final class LotteryTransferService
|
||||
string $action,
|
||||
string $remark = '',
|
||||
): void {
|
||||
if ($order->status !== self::ST_PENDING_RECONCILE) {
|
||||
$allowedStatuses = $action === 'manually_process'
|
||||
? [self::ST_PROCESSING, self::ST_FAILED, self::ST_PENDING_RECONCILE]
|
||||
: [self::ST_PENDING_RECONCILE];
|
||||
|
||||
if (! in_array($order->status, $allowedStatuses, true)) {
|
||||
throw new WalletOperationException(
|
||||
'order_not_pending_reconcile',
|
||||
ErrorCode::WalletExternalRejected->value,
|
||||
|
||||
39
app/Support/AdminAuthProfile.php
Normal file
39
app/Support/AdminAuthProfile.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
|
||||
final class AdminAuthProfile
|
||||
{
|
||||
/**
|
||||
* @return array{
|
||||
* id: int,
|
||||
* username: string,
|
||||
* nickname: string,
|
||||
* email: ?string,
|
||||
* permissions: list<string>,
|
||||
* navigation: list<array{
|
||||
* segment: string,
|
||||
* label: string,
|
||||
* href: string,
|
||||
* activeMatchPrefix?: string,
|
||||
* requiredAny?: list<string>
|
||||
* }>
|
||||
* }
|
||||
*/
|
||||
public static function fromAdmin(AdminUser $admin): array
|
||||
{
|
||||
$fresh = $admin->fresh();
|
||||
$permissionSlugs = $fresh->adminPermissionSlugs();
|
||||
|
||||
return [
|
||||
'id' => $fresh->id,
|
||||
'username' => $fresh->username,
|
||||
'nickname' => $fresh->name,
|
||||
'email' => $fresh->email,
|
||||
'permissions' => $permissionSlugs,
|
||||
'navigation' => AdminAuthorizationRegistry::visibleNavigationItems($permissionSlugs),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -8,55 +8,56 @@ final class AdminAuthorizationRegistry
|
||||
* 后台功能权限的单一注册表:
|
||||
* - `slug`:前端与后台用户权限管理仍使用的 legacy `prd.*`
|
||||
* - `permission_codes`:资源鉴权实际使用的 action code
|
||||
* - `group_key`:权限目录中的展示分组
|
||||
* - `nav_segment`:权限目录按后台导航分组展示
|
||||
*
|
||||
* @return list<array{
|
||||
* slug: string,
|
||||
* name: string,
|
||||
* group_key: string,
|
||||
* nav_segment: string,
|
||||
* permission_codes: list<string>
|
||||
* }>
|
||||
*/
|
||||
public static function permissionDefinitions(): array
|
||||
{
|
||||
return [
|
||||
['slug' => 'prd.users.manage', 'name' => '用户管理·可管理', 'group_key' => 'users_players', 'permission_codes' => ['service.players.manage']],
|
||||
['slug' => 'prd.users.view_finance', 'name' => '用户管理·财务查看', 'group_key' => 'users_players', 'permission_codes' => ['service.players.view', 'service.wallet.view']],
|
||||
['slug' => 'prd.users.view_cs', 'name' => '用户管理·客服单用户', 'group_key' => 'users_players', 'permission_codes' => ['service.players.view', 'service.tickets.view', 'service.wallet.view']],
|
||||
['slug' => 'prd.player_freeze.manage', 'name' => '冻结/解冻玩家·可管理', 'group_key' => 'users_players', 'permission_codes' => ['service.players.manage']],
|
||||
['slug' => 'prd.admin_user.manage', 'name' => '管理员列表·可管理', 'nav_segment' => 'admin_users', 'permission_codes' => ['system.admin_user.manage']],
|
||||
['slug' => 'prd.admin_role.manage', 'name' => '角色管理·可管理', 'nav_segment' => 'admin_roles', 'permission_codes' => ['system.admin_role.manage']],
|
||||
|
||||
['slug' => 'prd.play_switch.manage', 'name' => '玩法开关·可管理', 'group_key' => 'ops_config', 'permission_codes' => ['config.play.manage']],
|
||||
['slug' => 'prd.odds.manage', 'name' => '赔率配置·可管理', 'group_key' => 'ops_config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.risk_cap.manage', 'name' => '封顶配置·可管理', 'group_key' => 'ops_config', 'permission_codes' => ['config.risk_cap.manage']],
|
||||
['slug' => 'prd.risk_cap.view', 'name' => '封顶配置·查看', 'group_key' => 'ops_config', 'permission_codes' => ['config.risk_cap.view']],
|
||||
['slug' => 'prd.rebate.manage', 'name' => '佣金/回水·可管理', 'group_key' => 'ops_config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.rebate.view', 'name' => '佣金/回水·查看', 'group_key' => 'ops_config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.jackpot.manage', 'name' => 'Jackpot 配置·可管理', 'group_key' => 'ops_config', 'permission_codes' => ['config.jackpot.manage']],
|
||||
['slug' => 'prd.jackpot.view', 'name' => 'Jackpot 配置·查看', 'group_key' => 'ops_config', 'permission_codes' => ['config.jackpot.view']],
|
||||
['slug' => 'prd.users.manage', 'name' => '用户管理·可管理', 'nav_segment' => 'players', 'permission_codes' => ['service.players.manage']],
|
||||
['slug' => 'prd.users.view_finance', 'name' => '用户管理·财务查看', 'nav_segment' => 'players', 'permission_codes' => ['service.players.view', 'service.wallet.view']],
|
||||
['slug' => 'prd.users.view_cs', 'name' => '用户管理·客服单用户', 'nav_segment' => 'players', 'permission_codes' => ['service.players.view', 'service.tickets.view']],
|
||||
['slug' => 'prd.player_freeze.manage', 'name' => '冻结/解冻玩家·可管理', 'nav_segment' => 'players', 'permission_codes' => ['service.players.freeze']],
|
||||
|
||||
['slug' => 'prd.draw_result.manage', 'name' => '开奖结果录入·可管理', 'group_key' => 'draw_risk', 'permission_codes' => ['draw.results.view', 'draw.review.review', 'draw.review.publish', 'risk.monitor.view']],
|
||||
['slug' => 'prd.draw_result.view', 'name' => '开奖结果·查看', 'group_key' => 'draw_risk', 'permission_codes' => ['draw.results.view', 'risk.monitor.view']],
|
||||
['slug' => 'prd.draw_reopen.manage', 'name' => '开奖结果重开·可管理', 'group_key' => 'draw_risk', 'permission_codes' => ['draw.review.publish']],
|
||||
['slug' => 'prd.wallet_reconcile.manage', 'name' => '钱包对账·可管理', 'nav_segment' => 'wallet', 'permission_codes' => ['service.wallet.manage', 'service.reconcile.manage']],
|
||||
['slug' => 'prd.wallet_reconcile.view', 'name' => '钱包对账·查看', 'nav_segment' => 'wallet', 'permission_codes' => ['service.wallet.view', 'service.reconcile.view']],
|
||||
['slug' => 'prd.wallet_reconcile.view_cs', 'name' => '钱包对账·客服单用户', 'nav_segment' => 'wallet', 'permission_codes' => ['service.wallet.view', 'service.reconcile.view']],
|
||||
['slug' => 'prd.wallet_adjust.manage', 'name' => '补单/冲正·可管理', 'nav_segment' => 'wallet', 'permission_codes' => ['service.wallet.manage']],
|
||||
|
||||
['slug' => 'prd.payout.manage', 'name' => '派彩确认·可管理', 'group_key' => 'settlement', 'permission_codes' => ['settlement.batch.manage', 'settlement.batch.view']],
|
||||
['slug' => 'prd.payout.review', 'name' => '派彩确认·可审核', 'group_key' => 'settlement', 'permission_codes' => ['settlement.batch.review', 'settlement.batch.view']],
|
||||
['slug' => 'prd.payout.view', 'name' => '派彩确认·查看', 'group_key' => 'settlement', 'permission_codes' => ['settlement.batch.view']],
|
||||
['slug' => 'prd.draw_result.manage', 'name' => '开奖结果录入·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.results.view', 'draw.review.review', 'draw.review.publish', 'risk.monitor.view']],
|
||||
['slug' => 'prd.draw_result.view', 'name' => '开奖结果·查看', 'nav_segment' => 'draws', 'permission_codes' => ['draw.results.view', 'risk.monitor.view']],
|
||||
['slug' => 'prd.draw_reopen.manage', 'name' => '开奖结果重开·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.review.publish']],
|
||||
|
||||
['slug' => 'prd.wallet_reconcile.manage', 'name' => '钱包对账·可管理', 'group_key' => 'wallet', 'permission_codes' => ['service.wallet.manage', 'service.reconcile.manage']],
|
||||
['slug' => 'prd.wallet_reconcile.view', 'name' => '钱包对账·查看', 'group_key' => 'wallet', 'permission_codes' => ['service.wallet.view', 'service.reconcile.view']],
|
||||
['slug' => 'prd.wallet_reconcile.view_cs', 'name' => '钱包对账·客服单用户', 'group_key' => 'wallet', 'permission_codes' => ['service.wallet.view', 'service.reconcile.view']],
|
||||
['slug' => 'prd.wallet_adjust.manage', 'name' => '补单/冲正·可管理', 'group_key' => 'wallet', 'permission_codes' => ['service.wallet.manage']],
|
||||
['slug' => 'prd.play_switch.manage', 'name' => '玩法开关·可管理', 'nav_segment' => 'config', 'permission_codes' => ['config.play.manage']],
|
||||
['slug' => 'prd.odds.manage', 'name' => '赔率配置·可管理', 'nav_segment' => 'config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.risk_cap.manage', 'name' => '封顶配置·可管理', 'nav_segment' => 'config', 'permission_codes' => ['config.risk_cap.manage']],
|
||||
['slug' => 'prd.risk_cap.view', 'name' => '封顶配置·查看', 'nav_segment' => 'config', 'permission_codes' => ['config.risk_cap.view']],
|
||||
['slug' => 'prd.rebate.manage', 'name' => '佣金/回水·可管理', 'nav_segment' => 'config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.rebate.view', 'name' => '佣金/回水·查看', 'nav_segment' => 'config', 'permission_codes' => ['config.odds.manage']],
|
||||
['slug' => 'prd.jackpot.manage', 'name' => 'Jackpot 配置·可管理', 'nav_segment' => 'config', 'permission_codes' => ['config.jackpot.manage']],
|
||||
['slug' => 'prd.jackpot.view', 'name' => 'Jackpot 配置·查看', 'nav_segment' => 'config', 'permission_codes' => ['config.jackpot.view']],
|
||||
|
||||
['slug' => 'prd.report.all', 'name' => '报表·全部', 'group_key' => 'reports', 'permission_codes' => ['service.reports.view', 'service.reports.export']],
|
||||
['slug' => 'prd.report.risk', 'name' => '报表·风控', 'group_key' => 'reports', 'permission_codes' => ['service.reports.view']],
|
||||
['slug' => 'prd.report.finance', 'name' => '报表·财务', 'group_key' => 'reports', 'permission_codes' => ['service.reports.view', 'service.reports.export']],
|
||||
['slug' => 'prd.report.player', 'name' => '报表·单用户', 'group_key' => 'reports', 'permission_codes' => ['service.reports.view']],
|
||||
['slug' => 'prd.payout.manage', 'name' => '派彩确认·可管理', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.batch.manage', 'settlement.batch.view']],
|
||||
['slug' => 'prd.payout.review', 'name' => '派彩确认·可审核', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.batch.review', 'settlement.batch.view']],
|
||||
['slug' => 'prd.payout.view', 'name' => '派彩确认·查看', 'nav_segment' => 'settlement', 'permission_codes' => ['settlement.batch.view']],
|
||||
|
||||
['slug' => 'prd.audit.all', 'name' => '审计日志·全部', 'group_key' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.self', 'name' => '审计日志·自身相关', 'group_key' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.finance', 'name' => '审计日志·资金相关', 'group_key' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.report.all', 'name' => '报表·全部', 'nav_segment' => 'reports', 'permission_codes' => ['service.reports.view', 'service.reports.export']],
|
||||
['slug' => 'prd.report.risk', 'name' => '报表·风控', 'nav_segment' => 'reports', 'permission_codes' => ['service.reports.view']],
|
||||
['slug' => 'prd.report.finance', 'name' => '报表·财务', 'nav_segment' => 'reports', 'permission_codes' => ['service.reports.view', 'service.reports.export']],
|
||||
['slug' => 'prd.report.player', 'name' => '报表·单用户', 'nav_segment' => 'reports', 'permission_codes' => ['service.reports.view']],
|
||||
|
||||
['slug' => 'prd.admin_user.manage', 'name' => '后台用户权限管理·可管理', 'group_key' => 'system', 'permission_codes' => ['system.admin_user.manage']],
|
||||
['slug' => 'prd.audit.all', 'name' => '审计日志·全部', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.self', 'name' => '审计日志·自身相关', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
['slug' => 'prd.audit.finance', 'name' => '审计日志·资金相关', 'nav_segment' => 'audit', 'permission_codes' => ['service.audit.view']],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -68,16 +69,31 @@ final class AdminAuthorizationRegistry
|
||||
*/
|
||||
public static function permissionGroupDefinitions(): array
|
||||
{
|
||||
return [
|
||||
['key' => 'users_players', 'label' => '用户与玩家'],
|
||||
['key' => 'ops_config', 'label' => '运营配置'],
|
||||
['key' => 'draw_risk', 'label' => '开奖与风控'],
|
||||
['key' => 'settlement', 'label' => '结算与派彩'],
|
||||
['key' => 'wallet', 'label' => '钱包与对账'],
|
||||
['key' => 'reports', 'label' => '报表'],
|
||||
['key' => 'audit', 'label' => '审计日志'],
|
||||
['key' => 'system', 'label' => '系统管理'],
|
||||
$labels = [
|
||||
'dashboard' => '仪表盘',
|
||||
'admin_users' => '管理列表',
|
||||
'admin_roles' => '角色管理',
|
||||
'players' => '玩家列表',
|
||||
'wallet' => '钱包流水',
|
||||
'draws' => '期号列表',
|
||||
'config' => '运营配置',
|
||||
'risk' => '风控',
|
||||
'settlement' => '结算',
|
||||
'jackpot' => 'Jackpot',
|
||||
'reconcile' => '对账',
|
||||
'tickets' => '玩家注单',
|
||||
'reports' => '报表导出',
|
||||
'audit' => '审计日志',
|
||||
'settings' => '系统设置',
|
||||
];
|
||||
|
||||
return array_map(
|
||||
static fn (array $item): array => [
|
||||
'key' => $item['segment'],
|
||||
'label' => $labels[$item['segment']] ?? $item['label'],
|
||||
],
|
||||
self::navigationDefinitions(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,13 +112,13 @@ final class AdminAuthorizationRegistry
|
||||
return [
|
||||
['segment' => 'dashboard', 'label' => 'Dashboard', 'href' => '/admin'],
|
||||
['segment' => 'admin_users', 'label' => 'Admin Users', 'href' => '/admin/admin-users', 'requiredAny' => ['prd.admin_user.manage']],
|
||||
['segment' => 'admin_roles', 'label' => 'Admin Roles', 'href' => '/admin/admin-roles', 'requiredAny' => ['prd.admin_role.manage']],
|
||||
['segment' => 'players', 'label' => 'Players', 'href' => '/admin/players', 'requiredAny' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['segment' => 'wallet', 'label' => 'Wallet', 'href' => '/admin/wallet/transactions', 'activeMatchPrefix' => '/admin/wallet', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['segment' => 'wallet', 'label' => 'Wallet', 'href' => '/admin/wallet/transactions', 'activeMatchPrefix' => '/admin/wallet', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.users.manage', 'prd.users.view_finance']],
|
||||
['segment' => 'draws', 'label' => 'Draws', 'href' => '/admin/draws', 'requiredAny' => ['prd.draw_result.manage', 'prd.draw_result.view']],
|
||||
['segment' => 'config', 'label' => 'Configuration', 'href' => '/admin/config', 'requiredAny' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.risk_cap.manage', 'prd.risk_cap.view', 'prd.rebate.manage', 'prd.rebate.view', 'prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['segment' => 'risk', 'label' => 'Risk', 'href' => '/admin/risk', 'requiredAny' => ['prd.draw_result.view', 'prd.draw_result.manage']],
|
||||
['segment' => 'settlement', 'label' => 'Settlement', 'href' => '/admin/settlement-batches', 'requiredAny' => ['prd.payout.manage', 'prd.payout.review', 'prd.payout.view']],
|
||||
['segment' => 'jackpot', 'label' => 'Jackpot', 'href' => '/admin/jackpot/pools', 'activeMatchPrefix' => '/admin/jackpot', 'requiredAny' => ['prd.jackpot.manage', 'prd.jackpot.view']],
|
||||
['segment' => 'reconcile', 'label' => 'Reconcile', 'href' => '/admin/reconcile', 'requiredAny' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs']],
|
||||
['segment' => 'tickets', 'label' => 'Tickets', 'href' => '/admin/tickets', 'requiredAny' => ['prd.users.view_cs', 'prd.users.manage', 'prd.users.view_finance', 'prd.draw_result.view', 'prd.draw_result.manage', 'prd.payout.view', 'prd.payout.review', 'prd.payout.manage', 'prd.report.player']],
|
||||
['segment' => 'reports', 'label' => 'Reports', 'href' => '/admin/reports', 'requiredAny' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player']],
|
||||
@@ -141,14 +157,9 @@ final class AdminAuthorizationRegistry
|
||||
*/
|
||||
public static function permissionMenuGroups(): array
|
||||
{
|
||||
$permissionsByGroup = [];
|
||||
foreach (self::permissionDefinitions() as $permission) {
|
||||
$permissionsByGroup[$permission['group_key']][] = $permission['slug'];
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
foreach (self::permissionGroupDefinitions() as $group) {
|
||||
$slugs = $permissionsByGroup[$group['key']] ?? [];
|
||||
$slugs = self::permissionSlugsForNavigationSegment($group['key']);
|
||||
if ($slugs === []) {
|
||||
continue;
|
||||
}
|
||||
@@ -162,6 +173,38 @@ final class AdminAuthorizationRegistry
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private static function permissionSlugsForNavigationSegment(string $segment): array
|
||||
{
|
||||
$explicit = [
|
||||
'admin_users' => ['prd.admin_user.manage'],
|
||||
'admin_roles' => ['prd.admin_role.manage'],
|
||||
'players' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs', 'prd.player_freeze.manage'],
|
||||
'wallet' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs', 'prd.wallet_adjust.manage', 'prd.users.view_finance'],
|
||||
'draws' => ['prd.draw_result.manage', 'prd.draw_result.view', 'prd.draw_reopen.manage'],
|
||||
'config' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.risk_cap.manage', 'prd.risk_cap.view', 'prd.rebate.manage', 'prd.rebate.view', 'prd.jackpot.manage', 'prd.jackpot.view'],
|
||||
'risk' => ['prd.draw_result.manage', 'prd.draw_result.view'],
|
||||
'settlement' => ['prd.payout.manage', 'prd.payout.review', 'prd.payout.view'],
|
||||
'reconcile' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs'],
|
||||
'tickets' => ['prd.users.view_cs', 'prd.users.manage', 'prd.report.player'],
|
||||
'reports' => ['prd.report.all', 'prd.report.risk', 'prd.report.finance', 'prd.report.player'],
|
||||
'audit' => ['prd.audit.all', 'prd.audit.self', 'prd.audit.finance'],
|
||||
];
|
||||
|
||||
if (isset($explicit[$segment])) {
|
||||
return $explicit[$segment];
|
||||
}
|
||||
|
||||
$slugs = [];
|
||||
foreach (self::permissionDefinitions() as $permission) {
|
||||
if ($permission['nav_segment'] === $segment) {
|
||||
$slugs[] = $permission['slug'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($slugs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* segment: string,
|
||||
@@ -234,7 +277,7 @@ final class AdminAuthorizationRegistry
|
||||
'route_name' => $resource['route_name'],
|
||||
'auth_mode' => $resource['auth_mode'],
|
||||
'is_audit_required' => $resource['is_audit_required'],
|
||||
'permission_codes' => self::permissionCodesForLegacySlugs($resource['legacy_permission_slugs'] ?? []),
|
||||
'permission_codes' => $resource['permission_codes'] ?? self::permissionCodesForLegacySlugs($resource['legacy_permission_slugs'] ?? []),
|
||||
],
|
||||
self::resourceDefinitions(),
|
||||
);
|
||||
@@ -276,6 +319,7 @@ final class AdminAuthorizationRegistry
|
||||
return [
|
||||
['code' => 'admin.ping', 'module_code' => 'system', 'name' => '后台连通性探测', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/ping', 'route_name' => 'api.v1.admin.ping', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.dashboard', 'module_code' => 'dashboard', 'name' => '后台仪表盘', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/dashboard', 'route_name' => 'api.v1.admin.dashboard', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.auth.me', 'module_code' => 'system', 'name' => '后台当前管理员摘要', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/auth/me', 'route_name' => 'api.v1.admin.auth.me', 'auth_mode' => 'login_only', 'is_audit_required' => false],
|
||||
['code' => 'admin.audit.index', 'module_code' => 'audit', 'name' => '审计日志查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/audit-logs', 'route_name' => 'api.v1.admin.audit-logs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.audit.all', 'prd.audit.self', 'prd.audit.finance']],
|
||||
|
||||
['code' => 'admin.admin-users.index', 'module_code' => 'system', 'name' => '管理员列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-users', 'route_name' => 'api.v1.admin.admin-users.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
@@ -283,9 +327,14 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.admin-users.show', 'module_code' => 'system', 'name' => '管理员详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/admin-users/{admin_user}', 'route_name' => 'api.v1.admin.admin-users.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.admin_user.manage']],
|
||||
['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']],
|
||||
['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']],
|
||||
['code' => 'admin.admin-roles.update', 'module_code' => 'system', 'name' => '更新角色', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-roles/{admin_role}', 'route_name' => 'api.v1.admin.admin-roles.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
['code' => 'admin.admin-roles.destroy', 'module_code' => 'system', 'name' => '删除角色', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/admin-roles/{admin_role}', 'route_name' => 'api.v1.admin.admin-roles.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
['code' => 'admin.admin-roles.permissions.sync', 'module_code' => 'system', 'name' => '角色权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/admin-roles/{admin_role}/permissions', 'route_name' => 'api.v1.admin.admin-roles.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.admin_role.manage']],
|
||||
|
||||
['code' => 'admin.play-types.index', 'module_code' => 'config', 'name' => '玩法类型列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/play-types', 'route_name' => 'api.v1.admin.play-types.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.play_switch.manage', 'prd.odds.manage']],
|
||||
['code' => 'admin.play-types.patch', 'module_code' => 'config', 'name' => '玩法类型切换', 'http_method' => 'PATCH', 'uri_pattern' => '/api/v1/admin/play-types/{play_code}', 'route_name' => 'api.v1.admin.play-types.patch', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.play_switch.manage', 'prd.odds.manage', 'prd.risk_cap.manage', 'prd.rebate.manage', 'prd.jackpot.manage']],
|
||||
@@ -319,7 +368,7 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.draws.risk-pools.show', 'module_code' => 'risk', 'name' => '风控池详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/draws/{draw}/risk-pools/{number_4d}', 'route_name' => 'api.v1.admin.draws.risk-pools.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.draw_result.manage', 'prd.draw_result.view']],
|
||||
['code' => 'admin.draws.result-batches.store', 'module_code' => 'draw', 'name' => '创建开奖结果批次', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/result-batches', 'route_name' => 'api.v1.admin.draws.result-batches.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
['code' => 'admin.draws.result-batches.publish', 'module_code' => 'draw', 'name' => '发布开奖结果批次', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/result-batches/{batch}/publish', 'route_name' => 'api.v1.admin.draws.result-batches.publish', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
['code' => 'admin.draws.reopen', 'module_code' => 'draw', 'name' => '重开开奖', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/reopen', 'route_name' => 'api.v1.admin.draws.reopen', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
['code' => 'admin.draws.reopen', 'module_code' => 'draw', 'name' => '重开开奖', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/reopen', 'route_name' => 'api.v1.admin.draws.reopen', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_reopen.manage']],
|
||||
['code' => 'admin.draws.generate-plan', 'module_code' => 'draw', 'name' => '生成开奖计划', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/generate-plan', 'route_name' => 'api.v1.admin.draws.generate-plan', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
['code' => 'admin.draws.manual-close', 'module_code' => 'draw', 'name' => '人工封盘', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/manual-close', 'route_name' => 'api.v1.admin.draws.manual-close', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
['code' => 'admin.draws.risk-pools.manual-close', 'module_code' => 'risk', 'name' => '人工关闭风控池', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/risk-pools/{number_4d}/manual-close', 'route_name' => 'api.v1.admin.draws.risk-pools.manual-close', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.draw_result.manage']],
|
||||
@@ -342,15 +391,15 @@ final class AdminAuthorizationRegistry
|
||||
['code' => 'admin.jackpot.pools.update', 'module_code' => 'jackpot', 'name' => '更新奖池', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/jackpot/pools/{pool}', 'route_name' => 'api.v1.admin.jackpot.pools.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.jackpot.manage']],
|
||||
['code' => 'admin.jackpot.pools.manual-burst', 'module_code' => 'jackpot', 'name' => '手动爆池', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/jackpot/pools/{pool}/manual-burst', 'route_name' => 'api.v1.admin.jackpot.pools.manual-burst', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'legacy_permission_slugs' => ['prd.jackpot.manage']],
|
||||
|
||||
['code' => 'admin.players.index', 'module_code' => 'player_service', 'name' => '玩家列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players', 'route_name' => 'api.v1.admin.players.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['code' => 'admin.players.wallets', 'module_code' => 'player_service', 'name' => '玩家钱包查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/wallets', 'route_name' => 'api.v1.admin.players.wallets', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['code' => 'admin.players.ticket-items', 'module_code' => 'player_service', 'name' => '玩家注单查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/ticket-items', 'route_name' => 'api.v1.admin.players.ticket-items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.users.manage', 'prd.users.view_finance', 'prd.users.view_cs']],
|
||||
['code' => 'admin.players.index', 'module_code' => 'player_service', 'name' => '玩家列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players', 'route_name' => 'api.v1.admin.players.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.players.view']],
|
||||
['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, 'legacy_permission_slugs' => ['prd.users.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, 'legacy_permission_slugs' => ['prd.users.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, 'legacy_permission_slugs' => ['prd.users.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']],
|
||||
['code' => 'admin.players.wallets', 'module_code' => 'player_service', 'name' => '玩家钱包查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/wallets', 'route_name' => 'api.v1.admin.players.wallets', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.wallet.view']],
|
||||
['code' => 'admin.players.ticket-items', 'module_code' => 'player_service', 'name' => '玩家注单查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/ticket-items', 'route_name' => 'api.v1.admin.players.ticket-items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.tickets.view']],
|
||||
|
||||
['code' => 'admin.wallet.transfer-orders', 'module_code' => 'wallet', 'name' => '转账单查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transfer-orders', 'route_name' => 'api.v1.admin.wallet.transfer-orders', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs']],
|
||||
['code' => 'admin.wallet.transactions', 'module_code' => 'wallet', 'name' => '钱包流水查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transactions', 'route_name' => 'api.v1.admin.wallet.transactions', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'legacy_permission_slugs' => ['prd.wallet_reconcile.manage', 'prd.wallet_reconcile.view', 'prd.wallet_reconcile.view_cs']],
|
||||
|
||||
24
app/Support/AdminRoleApiPresenter.php
Normal file
24
app/Support/AdminRoleApiPresenter.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\AdminRole;
|
||||
|
||||
final class AdminRoleApiPresenter
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public static function item(AdminRole $role): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) $role->id,
|
||||
'slug' => $role->slug,
|
||||
'name' => $role->name,
|
||||
'description' => $role->description,
|
||||
'status' => (int) $role->status,
|
||||
'is_system' => (bool) $role->is_system,
|
||||
'sort_order' => (int) $role->sort_order,
|
||||
'permission_slugs' => $role->legacyPermissionSlugs(),
|
||||
'user_count' => $role->assignedUserCount(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -270,6 +270,7 @@ return new class extends Migration
|
||||
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.reports', 'name' => '报表导出', 'path' => '/admin/reports', 'route_name' => 'admin.reports.index', 'component' => 'service/reports', 'icon' => null, 'sort_order' => 50],
|
||||
['parent_code' => 'service', 'menu_type' => 'page', 'code' => 'service.audit', 'name' => '审计日志', 'path' => '/admin/audit-logs', 'route_name' => 'admin.audit.index', 'component' => 'service/audit', 'icon' => null, 'sort_order' => 60],
|
||||
['parent_code' => null, 'menu_type' => 'page', 'code' => 'system.admin_user', 'name' => '管理员权限', 'path' => '/admin/admin-users', 'route_name' => 'admin.system.admin-users', 'component' => 'system/admin-users', 'icon' => 'users-round', 'sort_order' => 70],
|
||||
['parent_code' => null, 'menu_type' => 'page', 'code' => 'system.admin_role', 'name' => '角色管理', 'path' => '/admin/admin-roles', 'route_name' => 'admin.system.admin-roles', 'component' => 'system/admin-roles', 'icon' => 'shield-check', 'sort_order' => 71],
|
||||
];
|
||||
|
||||
$menuIds = [];
|
||||
@@ -313,6 +314,7 @@ return new class extends Migration
|
||||
['menu_code' => 'settlement.batch', 'action_code' => 'manage', 'permission_code' => 'settlement.batch.manage', 'name' => '结算执行'],
|
||||
['menu_code' => 'service.players', 'action_code' => 'view', 'permission_code' => 'service.players.view', 'name' => '玩家查询查看'],
|
||||
['menu_code' => 'service.players', 'action_code' => 'manage', 'permission_code' => 'service.players.manage', 'name' => '玩家查询管理'],
|
||||
['menu_code' => 'service.players', 'action_code' => 'update', 'permission_code' => 'service.players.freeze', 'name' => '冻结解冻玩家'],
|
||||
['menu_code' => 'service.tickets', 'action_code' => 'view', 'permission_code' => 'service.tickets.view', 'name' => '玩家注单查看'],
|
||||
['menu_code' => 'service.wallet', 'action_code' => 'view', 'permission_code' => 'service.wallet.view', 'name' => '钱包流水查看'],
|
||||
['menu_code' => 'service.wallet', 'action_code' => 'manage', 'permission_code' => 'service.wallet.manage', 'name' => '钱包流水管理'],
|
||||
@@ -322,6 +324,7 @@ return new class extends Migration
|
||||
['menu_code' => 'service.reports', 'action_code' => 'export', 'permission_code' => 'service.reports.export', 'name' => '报表导出'],
|
||||
['menu_code' => 'service.audit', 'action_code' => 'view', 'permission_code' => 'service.audit.view', 'name' => '审计查看'],
|
||||
['menu_code' => 'system.admin_user', 'action_code' => 'manage', 'permission_code' => 'system.admin_user.manage', 'name' => '管理员权限管理'],
|
||||
['menu_code' => 'system.admin_role', 'action_code' => 'manage', 'permission_code' => 'system.admin_role.manage', 'name' => '角色权限管理'],
|
||||
];
|
||||
|
||||
foreach ($menuActions as $row) {
|
||||
@@ -347,8 +350,8 @@ return new class extends Migration
|
||||
['code' => 'admin.draws.settlement.run', 'module_code' => 'settlement', 'name' => '执行结算', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/draws/{draw}/settlement/run', 'route_name' => 'api.v1.admin.draws.settlement.run', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['settlement.batch.manage', 'settlement.batch.review']],
|
||||
['code' => 'admin.wallet.transactions', 'module_code' => 'wallet', 'name' => '钱包流水查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transactions', 'route_name' => 'api.v1.admin.wallet.transactions', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.wallet.view', 'service.wallet.manage']],
|
||||
['code' => 'admin.wallet.transfer-orders', 'module_code' => 'wallet', 'name' => '转账单查询', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/wallet/transfer-orders', 'route_name' => 'api.v1.admin.wallet.transfer-orders', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.wallet.view', 'service.wallet.manage']],
|
||||
['code' => 'admin.players.wallets', 'module_code' => 'player_service', 'name' => '玩家钱包查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/wallets', 'route_name' => 'api.v1.admin.players.wallets', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.view', 'service.players.manage']],
|
||||
['code' => 'admin.players.ticket-items', 'module_code' => 'player_service', 'name' => '玩家注单查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/ticket-items', 'route_name' => 'api.v1.admin.players.ticket-items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.tickets.view']],
|
||||
['code' => 'admin.players.wallets', 'module_code' => 'player_service', 'name' => '玩家钱包查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/wallets', 'route_name' => 'api.v1.admin.players.wallets', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.wallet.view']],
|
||||
['code' => 'admin.players.ticket-items', 'module_code' => 'player_service', 'name' => '玩家注单查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/players/{player}/ticket-items', 'route_name' => 'api.v1.admin.players.ticket-items.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.players.manage', 'service.tickets.view']],
|
||||
['code' => 'admin.reports.index', 'module_code' => 'report', 'name' => '报表任务列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.reports.view', 'service.reports.export']],
|
||||
['code' => 'admin.reports.store', 'module_code' => 'report', 'name' => '创建报表任务', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/report-jobs', 'route_name' => 'api.v1.admin.report-jobs.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['service.reports.export']],
|
||||
['code' => 'admin.reconcile.index', 'module_code' => 'reconcile', 'name' => '对账任务列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/reconcile-jobs', 'route_name' => 'api.v1.admin.reconcile-jobs.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['service.reconcile.view', 'service.reconcile.manage']],
|
||||
@@ -427,7 +430,7 @@ return new class extends Migration
|
||||
$legacyToNewPermissionMap = [
|
||||
'prd.users.manage' => ['service.players.manage'],
|
||||
'prd.users.view_finance' => ['service.players.view', 'service.wallet.view'],
|
||||
'prd.users.view_cs' => ['service.players.view', 'service.tickets.view', 'service.wallet.view'],
|
||||
'prd.users.view_cs' => ['service.players.view', 'service.tickets.view'],
|
||||
'prd.play_switch.manage' => ['config.play.manage'],
|
||||
'prd.odds.manage' => ['config.odds.manage'],
|
||||
'prd.risk_cap.manage' => ['config.risk_cap.manage'],
|
||||
@@ -452,7 +455,7 @@ return new class extends Migration
|
||||
'prd.audit.self' => ['service.audit.view'],
|
||||
'prd.audit.finance' => ['service.audit.view'],
|
||||
'prd.admin_user.manage' => ['system.admin_user.manage'],
|
||||
'prd.player_freeze.manage' => ['service.players.manage'],
|
||||
'prd.player_freeze.manage' => ['service.players.freeze'],
|
||||
'prd.wallet_adjust.manage' => ['service.wallet.manage'],
|
||||
'prd.draw_reopen.manage' => ['draw.review.publish'],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$currencyCodes = DB::table('currencies')
|
||||
->where('is_enabled', true)
|
||||
->where('is_bettable', true)
|
||||
->pluck('code')
|
||||
->filter(static fn ($code): bool => is_string($code) && trim($code) !== '')
|
||||
->map(static fn (string $code): string => strtoupper($code))
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($currencyCodes->isEmpty()) {
|
||||
$currencyCodes = collect([strtoupper((string) config('lottery.default_currency', 'NPR'))]);
|
||||
}
|
||||
|
||||
foreach ($currencyCodes as $currencyCode) {
|
||||
$exists = DB::table('jackpot_pools')->where('currency_code', $currencyCode)->exists();
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('jackpot_pools')->insert([
|
||||
'currency_code' => $currencyCode,
|
||||
'current_amount' => 0,
|
||||
'contribution_rate' => '0.0200',
|
||||
'trigger_threshold' => 100_000_000,
|
||||
'payout_rate' => '0.5000',
|
||||
'force_trigger_draw_gap' => 100,
|
||||
'min_bet_amount' => 100,
|
||||
'status' => 0,
|
||||
'last_trigger_draw_id' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 保留奖池配置与水位,避免回滚误删运营数据。
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use App\Support\AdminPermissionBridge;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('admin_role_legacy_permissions', function (Blueprint $table): void {
|
||||
$table->foreignId('role_id')->constrained('admin_roles')->cascadeOnDelete();
|
||||
$table->string('permission_slug', 128);
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['role_id', 'permission_slug'], 'pk_admin_role_legacy_permissions');
|
||||
});
|
||||
|
||||
$now = now();
|
||||
$roleCodes = DB::table('admin_role_menu_actions as rma')
|
||||
->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id')
|
||||
->where('ma.status', 1)
|
||||
->select('rma.role_id', 'ma.permission_code')
|
||||
->get()
|
||||
->groupBy('role_id');
|
||||
|
||||
foreach ($roleCodes as $roleId => $rows) {
|
||||
$slugs = AdminPermissionBridge::legacySlugsGrantedByMenuActionCodes(
|
||||
$rows->pluck('permission_code')->all(),
|
||||
);
|
||||
foreach ($slugs as $slug) {
|
||||
DB::table('admin_role_legacy_permissions')->insert([
|
||||
'role_id' => (int) $roleId,
|
||||
'permission_slug' => $slug,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('admin_role_legacy_permissions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
use App\Support\AdminPermissionBridge;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$actionCatalogId = (int) DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
|
||||
$adminUserMenuId = (int) DB::table('admin_menus')->where('code', 'system.admin_user')->value('id');
|
||||
|
||||
if ($adminUserMenuId > 0) {
|
||||
$adminUserMenu = DB::table('admin_menus')->where('id', $adminUserMenuId)->first();
|
||||
DB::table('admin_menus')->updateOrInsert(
|
||||
['code' => 'system.admin_role'],
|
||||
[
|
||||
'parent_id' => $adminUserMenu->parent_id,
|
||||
'menu_type' => 'page',
|
||||
'name' => '角色管理',
|
||||
'path' => '/admin/admin-roles',
|
||||
'route_name' => 'admin.system.admin-roles',
|
||||
'component' => 'system/admin-roles',
|
||||
'icon' => 'shield-check',
|
||||
'active_menu_code' => null,
|
||||
'sort_order' => ((int) $adminUserMenu->sort_order) + 1,
|
||||
'is_visible' => true,
|
||||
'is_cache' => false,
|
||||
'is_external' => false,
|
||||
'status' => 1,
|
||||
'meta_json' => null,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$menuId = (int) DB::table('admin_menus')->where('code', 'system.admin_role')->value('id');
|
||||
|
||||
if ($actionCatalogId > 0 && $menuId > 0) {
|
||||
DB::table('admin_menu_actions')->updateOrInsert(
|
||||
['permission_code' => 'system.admin_role.manage'],
|
||||
[
|
||||
'menu_id' => $menuId,
|
||||
'action_id' => $actionCatalogId,
|
||||
'name' => '角色权限管理',
|
||||
'status' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||
foreach (AdminAuthorizationRegistry::resources() as $resource) {
|
||||
$resourceId = DB::table('admin_api_resources')
|
||||
->where('code', $resource['code'])
|
||||
->value('id');
|
||||
|
||||
if ($resourceId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')
|
||||
->where('api_resource_id', (int) $resourceId)
|
||||
->delete();
|
||||
|
||||
foreach ($resource['permission_codes'] as $permissionCode) {
|
||||
$menuActionId = $menuActionIds[$permissionCode] ?? null;
|
||||
if ($menuActionId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')->insert([
|
||||
'api_resource_id' => (int) $resourceId,
|
||||
'menu_action_id' => (int) $menuActionId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$adminRoleSlug = 'prd.admin_role.manage';
|
||||
$adminUserSlug = 'prd.admin_user.manage';
|
||||
$roleIds = DB::table('admin_role_legacy_permissions')
|
||||
->where('permission_slug', $adminUserSlug)
|
||||
->pluck('role_id')
|
||||
->all();
|
||||
|
||||
foreach ($roleIds as $roleId) {
|
||||
DB::table('admin_role_legacy_permissions')->updateOrInsert(
|
||||
[
|
||||
'role_id' => (int) $roleId,
|
||||
'permission_slug' => $adminRoleSlug,
|
||||
],
|
||||
[
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
);
|
||||
|
||||
foreach (AdminPermissionBridge::menuActionCodesForLegacy($adminRoleSlug) as $permissionCode) {
|
||||
$menuActionId = $menuActionIds[$permissionCode] ?? null;
|
||||
if ($menuActionId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_role_menu_actions')->updateOrInsert([
|
||||
'role_id' => (int) $roleId,
|
||||
'menu_action_id' => (int) $menuActionId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 不回滚授权数据,避免删除线上已经显式授予的角色管理权限。
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use App\Support\AdminAuthorizationRegistry;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$playersMenuId = (int) DB::table('admin_menus')->where('code', 'service.players')->value('id');
|
||||
$updateActionId = (int) DB::table('admin_action_catalog')->where('code', 'update')->value('id');
|
||||
|
||||
if ($playersMenuId > 0 && $updateActionId > 0) {
|
||||
DB::table('admin_menu_actions')->updateOrInsert(
|
||||
['permission_code' => 'service.players.freeze'],
|
||||
[
|
||||
'menu_id' => $playersMenuId,
|
||||
'action_id' => $updateActionId,
|
||||
'name' => '冻结解冻玩家',
|
||||
'status' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||
|
||||
$playerResourceBindings = [
|
||||
'admin.players.index' => ['service.players.manage', 'service.players.view'],
|
||||
'admin.players.store' => ['service.players.manage'],
|
||||
'admin.players.show' => ['service.players.manage', 'service.players.view'],
|
||||
'admin.players.update' => ['service.players.manage'],
|
||||
'admin.players.destroy' => ['service.players.manage'],
|
||||
'admin.players.freeze' => ['service.players.freeze'],
|
||||
'admin.players.unfreeze' => ['service.players.freeze'],
|
||||
'admin.players.wallets' => ['service.players.manage', 'service.wallet.view'],
|
||||
'admin.players.ticket-items' => ['service.players.manage', 'service.tickets.view'],
|
||||
];
|
||||
|
||||
foreach (AdminAuthorizationRegistry::resources() as $resource) {
|
||||
if (($resource['module_code'] ?? null) !== 'player_service') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$resourceId = DB::table('admin_api_resources')
|
||||
->where('code', $resource['code'])
|
||||
->value('id');
|
||||
|
||||
if ($resourceId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')
|
||||
->where('api_resource_id', (int) $resourceId)
|
||||
->delete();
|
||||
|
||||
$permissionCodes = $playerResourceBindings[$resource['code']] ?? $resource['permission_codes'];
|
||||
foreach ($permissionCodes as $permissionCode) {
|
||||
$menuActionId = $menuActionIds[$permissionCode] ?? null;
|
||||
if ($menuActionId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('admin_api_resource_bindings')->insert([
|
||||
'api_resource_id' => (int) $resourceId,
|
||||
'menu_action_id' => (int) $menuActionId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// 不回滚授权绑定,避免误删线上已调整的资源权限关系。
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Api\V1\Admin\Auth\MeController;
|
||||
use App\Http\Controllers\Api\V1\Admin\Audit\AuditLogIndexController;
|
||||
use App\Http\Controllers\Api\V1\Admin\Dashboard\AdminDashboardController;
|
||||
use App\Http\Controllers\Api\V1\Admin\PingController as AdminPingController;
|
||||
@@ -19,6 +20,11 @@ Route::get('dashboard', AdminDashboardController::class)
|
||||
->middleware('admin.api-resource')
|
||||
->name('api.v1.admin.dashboard');
|
||||
|
||||
// 当前管理员摘要
|
||||
Route::get('auth/me', MeController::class)
|
||||
->middleware('admin.api-resource')
|
||||
->name('api.v1.admin.auth.me');
|
||||
|
||||
// 审计日志
|
||||
Route::middleware('admin.api-resource')
|
||||
->get('audit-logs', AuditLogIndexController::class)
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminUserShowController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminUserIndexController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminRoleIndexController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminRoleStoreController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminRoleUpdateController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminRoleDestroyController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminRolePermissionSyncController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminUserStoreController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminUserUpdateController;
|
||||
use App\Http\Controllers\Api\V1\Admin\User\AdminUserDestroyController;
|
||||
@@ -31,4 +36,14 @@ Route::middleware('admin.api-resource')
|
||||
->name('api.v1.admin.admin-users.permissions.sync');
|
||||
Route::put('admin-users/{admin_user}/roles', AdminUserRoleSyncController::class)
|
||||
->name('api.v1.admin.admin-users.roles.sync');
|
||||
Route::get('admin-roles', AdminRoleIndexController::class)
|
||||
->name('api.v1.admin.admin-roles.index');
|
||||
Route::post('admin-roles', AdminRoleStoreController::class)
|
||||
->name('api.v1.admin.admin-roles.store');
|
||||
Route::put('admin-roles/{admin_role}', AdminRoleUpdateController::class)
|
||||
->name('api.v1.admin.admin-roles.update');
|
||||
Route::delete('admin-roles/{admin_role}', AdminRoleDestroyController::class)
|
||||
->name('api.v1.admin.admin-roles.destroy');
|
||||
Route::put('admin-roles/{admin_role}/permissions', AdminRolePermissionSyncController::class)
|
||||
->name('api.v1.admin.admin-roles.permissions.sync');
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use App\Models\AdminUser;
|
||||
use App\Lottery\ErrorCode;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
@@ -13,6 +14,52 @@ test('admin ping requires authentication', function () {
|
||||
->assertJsonPath('code', ErrorCode::AdminUnauthenticated->value);
|
||||
});
|
||||
|
||||
test('admin auth me returns current admin profile', function () {
|
||||
$admin = AdminUser::query()->create([
|
||||
'username' => 'admin_me',
|
||||
'name' => '管理员本人',
|
||||
'email' => null,
|
||||
'password' => 'secret-strong',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$roleId = DB::table('admin_roles')->insertGetId([
|
||||
'code' => 'super_admin',
|
||||
'slug' => 'super_admin',
|
||||
'name' => '超级管理员',
|
||||
'description' => null,
|
||||
'status' => 1,
|
||||
'is_system' => true,
|
||||
'sort_order' => 0,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$siteId = DB::table('admin_sites')->insertGetId([
|
||||
'code' => 'default',
|
||||
'name' => '默认站点',
|
||||
'is_default' => true,
|
||||
'status' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('admin_user_site_roles')->insert([
|
||||
'admin_user_id' => $admin->id,
|
||||
'site_id' => $siteId,
|
||||
'role_id' => $roleId,
|
||||
'granted_at' => now(),
|
||||
]);
|
||||
|
||||
$token = $admin->createToken('admin-api', ['*'], now()->addDay())->plainTextToken;
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/auth/me')
|
||||
->assertOk()
|
||||
->assertJsonPath('code', ErrorCode::Success->value)
|
||||
->assertJsonPath('data.admin.username', 'admin_me')
|
||||
->assertJsonPath('data.admin.navigation.0.segment', 'dashboard');
|
||||
});
|
||||
|
||||
test('admin login returns bearer token when captcha passes validation', function () {
|
||||
AdminUser::query()->create([
|
||||
'username' => 'tester',
|
||||
|
||||
@@ -4,6 +4,7 @@ use App\Models\AuditLog;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\Player;
|
||||
use App\Models\PlayerWallet;
|
||||
use App\Models\AdminRole;
|
||||
use App\Services\AuditLogger;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -24,6 +25,39 @@ function playerManageAdminToken(): string
|
||||
return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||
}
|
||||
|
||||
function playerPermissionAdminToken(string $username, array $permissionSlugs): string
|
||||
{
|
||||
$admin = AdminUser::query()->create([
|
||||
'username' => $username,
|
||||
'name' => 'Player Permission Admin',
|
||||
'email' => null,
|
||||
'password' => Hash::make('secret-strong'),
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$role = AdminRole::query()->create([
|
||||
'slug' => 'role_'.$username,
|
||||
'name' => 'Role '.$username,
|
||||
]);
|
||||
$role->syncLegacyPermissionSlugs($permissionSlugs);
|
||||
|
||||
$admin->roles()->sync([
|
||||
(int) $role->id => [
|
||||
'site_id' => AdminUser::defaultAdminSiteId(),
|
||||
'granted_at' => now(),
|
||||
],
|
||||
]);
|
||||
|
||||
return $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||
}
|
||||
|
||||
function playerPermissionRequest($test, string $token)
|
||||
{
|
||||
app('auth')->forgetGuards();
|
||||
|
||||
return $test->withHeader('Authorization', 'Bearer '.$token);
|
||||
}
|
||||
|
||||
test('admin can freeze and unfreeze player with audit log', function (): void {
|
||||
$player = Player::query()->create([
|
||||
'site_code' => 'main',
|
||||
@@ -65,3 +99,84 @@ test('admin can freeze and unfreeze player with audit log', function (): void {
|
||||
|
||||
expect(AuditLog::query()->where('module_code', 'player_manage')->count())->toBe(2);
|
||||
});
|
||||
|
||||
test('player manage permission gates write and freeze APIs separately from view permissions', function (): void {
|
||||
$player = Player::query()->create([
|
||||
'site_code' => 'main',
|
||||
'site_player_id' => 'perm-1',
|
||||
'username' => 'perm_user',
|
||||
'nickname' => 'Perm',
|
||||
'default_currency' => 'NPR',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$financeToken = playerPermissionAdminToken('player_finance_viewer', ['prd.users.view_finance']);
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->getJson('/api/v1/admin/players?per_page=10')
|
||||
->assertOk();
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
|
||||
->assertOk();
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
|
||||
->assertForbidden();
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->postJson('/api/v1/admin/players', [
|
||||
'site_code' => 'main',
|
||||
'site_player_id' => 'created-by-finance',
|
||||
'default_currency' => 'NPR',
|
||||
])
|
||||
->assertForbidden();
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->putJson('/api/v1/admin/players/'.$player->id, ['nickname' => 'blocked'])
|
||||
->assertForbidden();
|
||||
|
||||
playerPermissionRequest($this, $financeToken)
|
||||
->postJson('/api/v1/admin/players/'.$player->id.'/freeze')
|
||||
->assertForbidden();
|
||||
|
||||
$csToken = playerPermissionAdminToken('player_cs_viewer', ['prd.users.view_cs']);
|
||||
|
||||
playerPermissionRequest($this, $csToken)
|
||||
->getJson('/api/v1/admin/players?per_page=10')
|
||||
->assertOk();
|
||||
|
||||
playerPermissionRequest($this, $csToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
|
||||
->assertOk();
|
||||
|
||||
playerPermissionRequest($this, $csToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
|
||||
->assertForbidden();
|
||||
|
||||
$freezeToken = playerPermissionAdminToken('player_freezer', ['prd.player_freeze.manage']);
|
||||
|
||||
playerPermissionRequest($this, $freezeToken)
|
||||
->getJson('/api/v1/admin/players?per_page=10')
|
||||
->assertForbidden();
|
||||
|
||||
playerPermissionRequest($this, $freezeToken)
|
||||
->postJson('/api/v1/admin/players/'.$player->id.'/freeze')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', 1);
|
||||
|
||||
playerPermissionRequest($this, $freezeToken)
|
||||
->postJson('/api/v1/admin/players/'.$player->id.'/unfreeze')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', 0);
|
||||
|
||||
$manageToken = playerPermissionAdminToken('player_manager', ['prd.users.manage']);
|
||||
|
||||
playerPermissionRequest($this, $manageToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/wallets')
|
||||
->assertOk();
|
||||
|
||||
playerPermissionRequest($this, $manageToken)
|
||||
->getJson('/api/v1/admin/players/'.$player->id.'/ticket-items')
|
||||
->assertOk();
|
||||
});
|
||||
|
||||
@@ -28,29 +28,23 @@ test('admin settlement batches index is authenticated', function (): void {
|
||||
});
|
||||
|
||||
test('admin jackpot pools index returns rows', function (): void {
|
||||
JackpotPool::query()->create([
|
||||
'currency_code' => 'NPR',
|
||||
'current_amount' => 100,
|
||||
'contribution_rate' => '0.01',
|
||||
'trigger_threshold' => 1000,
|
||||
'payout_rate' => '0.5',
|
||||
'force_trigger_draw_gap' => 10,
|
||||
'min_bet_amount' => 0,
|
||||
'status' => 1,
|
||||
'last_trigger_draw_id' => null,
|
||||
]);
|
||||
|
||||
$token = mintSettlementAdminToken();
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/jackpot/pools')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.items.0.currency_code', 'NPR')
|
||||
->assertJsonPath('data.items.0.contribution_rate', '0.0200')
|
||||
->assertJsonPath('data.items.0.trigger_threshold', 100000000)
|
||||
->assertJsonPath('data.items.0.payout_rate', '0.5000')
|
||||
->assertJsonPath('data.items.0.force_trigger_draw_gap', 100)
|
||||
->assertJsonPath('data.items.0.min_bet_amount', 100)
|
||||
->assertJsonPath('data.items.0.status', 0)
|
||||
->assertJsonPath('data.items.0.combo_trigger_play_codes', []);
|
||||
});
|
||||
|
||||
test('admin can update jackpot combo trigger and manually burst pool', function (): void {
|
||||
$pool = JackpotPool::query()->create([
|
||||
'currency_code' => 'NPR',
|
||||
$pool = JackpotPool::query()->where('currency_code', 'NPR')->firstOrFail();
|
||||
$pool->forceFill([
|
||||
'current_amount' => 1000,
|
||||
'contribution_rate' => '0.01',
|
||||
'trigger_threshold' => 1000,
|
||||
@@ -59,7 +53,7 @@ test('admin can update jackpot combo trigger and manually burst pool', function
|
||||
'min_bet_amount' => 0,
|
||||
'status' => 1,
|
||||
'last_trigger_draw_id' => null,
|
||||
]);
|
||||
])->save();
|
||||
$draw = Draw::query()->create([
|
||||
'draw_no' => '20260518-001',
|
||||
'business_date' => '2026-05-18',
|
||||
|
||||
@@ -25,23 +25,7 @@ function makeAdminWithPermissions(string $username, array $permissionSlugs): str
|
||||
'name' => 'Role '.$username,
|
||||
]);
|
||||
|
||||
$codes = [];
|
||||
foreach ($permissionSlugs as $slug) {
|
||||
$codes = array_merge($codes, AdminPermissionBridge::menuActionCodesForLegacy($slug));
|
||||
}
|
||||
$codes = array_values(array_unique($codes));
|
||||
$ids = DB::table('admin_menu_actions')
|
||||
->whereIn('permission_code', $codes)
|
||||
->where('status', 1)
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
foreach ($ids as $mid) {
|
||||
DB::table('admin_role_menu_actions')->insert([
|
||||
'role_id' => $role->id,
|
||||
'menu_action_id' => (int) $mid,
|
||||
]);
|
||||
}
|
||||
$role->syncLegacyPermissionSlugs($permissionSlugs);
|
||||
|
||||
$siteId = AdminUser::defaultAdminSiteId();
|
||||
$admin->roles()->sync([
|
||||
@@ -129,7 +113,7 @@ test('admin can list users and sync direct permissions', function (): void {
|
||||
});
|
||||
|
||||
test('admin can sync user roles for default site', function (): void {
|
||||
$token = makeAdminWithPermissions('rbac_role_editor', ['prd.admin_user.manage']);
|
||||
$token = makeAdminWithPermissions('rbac_role_editor', ['prd.admin_user.manage', 'prd.admin_role.manage']);
|
||||
|
||||
$r1 = AdminRole::query()->create(['slug' => 'role_sync_a', 'name' => 'Role A']);
|
||||
$r2 = AdminRole::query()->create(['slug' => 'role_sync_b', 'name' => 'Role B']);
|
||||
@@ -154,6 +138,104 @@ test('admin can sync user roles for default site', function (): void {
|
||||
expect($slugs)->toBe(['role_sync_a', 'role_sync_b']);
|
||||
});
|
||||
|
||||
test('permission catalog groups permissions by admin navigation order', function (): void {
|
||||
$token = makeAdminWithPermissions('nav_group_catalog', ['prd.admin_user.manage', 'prd.admin_role.manage']);
|
||||
|
||||
$groups = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/admin-user-permission-catalog')
|
||||
->assertOk()
|
||||
->json('data.permission_menu_groups');
|
||||
|
||||
expect(array_column($groups, 'key'))->toBe([
|
||||
'admin_users',
|
||||
'admin_roles',
|
||||
'players',
|
||||
'wallet',
|
||||
'draws',
|
||||
'config',
|
||||
'risk',
|
||||
'settlement',
|
||||
'reconcile',
|
||||
'tickets',
|
||||
'reports',
|
||||
'audit',
|
||||
]);
|
||||
expect($groups[0]['label'])->toBe('管理列表');
|
||||
expect(array_column($groups[0]['permissions'], 'slug'))->toBe(['prd.admin_user.manage']);
|
||||
expect($groups[1]['label'])->toBe('角色管理');
|
||||
expect(array_column($groups[1]['permissions'], 'slug'))->toBe(['prd.admin_role.manage']);
|
||||
|
||||
$groupsByKey = collect($groups)->keyBy('key');
|
||||
expect(array_column($groupsByKey['tickets']['permissions'], 'slug'))->toBe([
|
||||
'prd.users.view_cs',
|
||||
'prd.users.manage',
|
||||
'prd.report.player',
|
||||
]);
|
||||
expect(array_column($groupsByKey['config']['permissions'], 'slug'))->toContain(
|
||||
'prd.jackpot.manage',
|
||||
'prd.jackpot.view',
|
||||
);
|
||||
expect(array_column($groupsByKey['reconcile']['permissions'], 'slug'))->toBe([
|
||||
'prd.wallet_reconcile.manage',
|
||||
'prd.wallet_reconcile.view',
|
||||
'prd.wallet_reconcile.view_cs',
|
||||
]);
|
||||
});
|
||||
|
||||
test('admin can repair role permissions from the full catalog after role creation', function (): void {
|
||||
$token = makeAdminWithPermissions('role_permission_repairer', ['prd.admin_user.manage', 'prd.admin_role.manage']);
|
||||
|
||||
$catalog = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/admin-user-permission-catalog')
|
||||
->assertOk()
|
||||
->json('data');
|
||||
|
||||
$catalogSlugs = collect($catalog['permission_menu_groups'])
|
||||
->flatMap(static fn (array $group): array => array_column($group['permissions'], 'slug'))
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
expect($catalogSlugs)
|
||||
->toContain('prd.admin_user.manage')
|
||||
->toContain('prd.admin_role.manage')
|
||||
->toContain('prd.report.player')
|
||||
->toContain('prd.wallet_reconcile.manage');
|
||||
|
||||
$role = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->postJson('/api/v1/admin/admin-roles', [
|
||||
'slug' => 'repairable_role',
|
||||
'name' => 'Repairable Role',
|
||||
'permission_slugs' => [],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.permission_slugs', [])
|
||||
->json('data');
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/admin/admin-roles/'.$role['id'].'/permissions', [
|
||||
'permission_slugs' => ['prd.report.player', 'prd.wallet_reconcile.manage'],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.slug', 'repairable_role')
|
||||
->assertJsonPath('data.permission_slugs', ['prd.report.player', 'prd.wallet_reconcile.manage']);
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->putJson('/api/v1/admin/admin-roles/'.$role['id'].'/permissions', [
|
||||
'permission_slugs' => ['prd.admin_role.manage'],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('data.permission_slugs', ['prd.admin_role.manage']);
|
||||
|
||||
$persistedPermissions = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/admin-roles')
|
||||
->assertOk()
|
||||
->json('data.items');
|
||||
|
||||
$persistedRole = collect($persistedPermissions)->firstWhere('slug', 'repairable_role');
|
||||
expect($persistedRole['permission_slugs'])->toBe(['prd.admin_role.manage']);
|
||||
});
|
||||
|
||||
test('admin can create update and delete users with crud rules', function (): void {
|
||||
$token = makeAdminWithPermissions('crud_actor', ['prd.admin_user.manage']);
|
||||
|
||||
|
||||
@@ -109,6 +109,109 @@ test('admin filters abnormal transfer orders', function (): void {
|
||||
$resp->assertOk()->assertJsonPath('data.total', 2);
|
||||
});
|
||||
|
||||
test('admin transfer order list exposes available reconcile actions by status', function (): void {
|
||||
$token = makeAdminToken();
|
||||
|
||||
$player = Player::query()->create([
|
||||
'site_code' => 'main',
|
||||
'site_player_id' => 'action-player',
|
||||
'username' => null,
|
||||
'nickname' => null,
|
||||
'default_currency' => 'NPR',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
foreach (
|
||||
[
|
||||
['TI_processing', 'processing'],
|
||||
['TI_failed', 'failed'],
|
||||
['TI_wait', 'pending_reconcile'],
|
||||
['TI_done', 'success'],
|
||||
] as [$no, $st]
|
||||
) {
|
||||
TransferOrder::query()->create([
|
||||
'transfer_no' => $no,
|
||||
'player_id' => $player->id,
|
||||
'direction' => 'in',
|
||||
'currency_code' => 'NPR',
|
||||
'amount' => 100,
|
||||
'idempotent_key' => 'action-'.$no,
|
||||
'status' => $st,
|
||||
'external_request_payload' => null,
|
||||
'external_response_payload' => null,
|
||||
'external_ref_no' => null,
|
||||
'fail_reason' => null,
|
||||
'finished_at' => $st === 'success' ? now() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
$items = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/admin/wallet/transfer-orders?per_page=10')
|
||||
->assertOk()
|
||||
->json('data.items');
|
||||
|
||||
$byNo = collect($items)->keyBy('transfer_no');
|
||||
|
||||
expect($byNo['TI_processing']['can_reverse'])->toBeFalse()
|
||||
->and($byNo['TI_processing']['can_manually_process'])->toBeTrue()
|
||||
->and($byNo['TI_failed']['can_reverse'])->toBeFalse()
|
||||
->and($byNo['TI_failed']['can_manually_process'])->toBeTrue()
|
||||
->and($byNo['TI_wait']['can_reverse'])->toBeTrue()
|
||||
->and($byNo['TI_wait']['can_manually_process'])->toBeTrue()
|
||||
->and($byNo['TI_done']['can_reverse'])->toBeFalse()
|
||||
->and($byNo['TI_done']['can_manually_process'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('admin can manually process abnormal transfer orders except completed ones', function (): void {
|
||||
$token = makeAdminToken();
|
||||
|
||||
$player = Player::query()->create([
|
||||
'site_code' => 'main',
|
||||
'site_player_id' => 'manual-player',
|
||||
'username' => null,
|
||||
'nickname' => null,
|
||||
'default_currency' => 'NPR',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
foreach (
|
||||
[
|
||||
['TI_processing_manual', 'processing'],
|
||||
['TI_failed_manual', 'failed'],
|
||||
['TI_success_manual', 'success'],
|
||||
] as [$no, $st]
|
||||
) {
|
||||
TransferOrder::query()->create([
|
||||
'transfer_no' => $no,
|
||||
'player_id' => $player->id,
|
||||
'direction' => 'in',
|
||||
'currency_code' => 'NPR',
|
||||
'amount' => 100,
|
||||
'idempotent_key' => 'manual-'.$no,
|
||||
'status' => $st,
|
||||
'external_request_payload' => null,
|
||||
'external_response_payload' => null,
|
||||
'external_ref_no' => null,
|
||||
'fail_reason' => null,
|
||||
'finished_at' => $st === 'success' ? now() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->postJson('/api/v1/admin/wallet/transfer-orders/TI_processing_manual/manually-process')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', 'manually_processed');
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->postJson('/api/v1/admin/wallet/transfer-orders/TI_failed_manual/manually-process')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.status', 'manually_processed');
|
||||
|
||||
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->postJson('/api/v1/admin/wallet/transfer-orders/TI_success_manual/manually-process')
|
||||
->assertStatus(422);
|
||||
});
|
||||
|
||||
test('admin lists wallet transactions and filters abnormal', function (): void {
|
||||
$token = makeAdminToken();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user