feat: 增强管理员权限与角色管理功能
- 在 SyncAdminAuthorizationCommand 中新增对代理和抽奖菜单操作的同步功能,确保缺失的菜单操作行能够被创建。 - 更新多个控制器中的权限检查逻辑,使用 hasPermissionCode 替代原有的权限验证方式,提升权限管理的灵活性。 - 引入 ApiMessage 统一错误响应格式,确保在权限不足时返回一致的错误信息。 - 更新 AdminRole 和 AdminUser 模型,增强角色与用户的权限管理功能,支持更细粒度的权限控制。
This commit is contained in:
@@ -5,7 +5,9 @@ namespace App\Console\Commands;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Support\AdminAgentPermissionMenuActionSync;
|
||||||
use App\Support\AdminAuthorizationRegistry;
|
use App\Support\AdminAuthorizationRegistry;
|
||||||
|
use App\Support\AdminDrawPermissionMenuActionSync;
|
||||||
|
|
||||||
final class SyncAdminAuthorizationCommand extends Command
|
final class SyncAdminAuthorizationCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -17,6 +19,16 @@ final class SyncAdminAuthorizationCommand extends Command
|
|||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
|
$agentCreated = AdminAgentPermissionMenuActionSync::syncMissing();
|
||||||
|
if ($agentCreated > 0) {
|
||||||
|
$this->info(sprintf('Created %d missing agent menu_action row(s).', $agentCreated));
|
||||||
|
}
|
||||||
|
|
||||||
|
$drawCreated = AdminDrawPermissionMenuActionSync::syncMissing();
|
||||||
|
if ($drawCreated > 0) {
|
||||||
|
$this->info(sprintf('Created %d missing draw menu_action row(s).', $drawCreated));
|
||||||
|
}
|
||||||
|
|
||||||
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||||
|
|
||||||
foreach (AdminAuthorizationRegistry::resources() as $resource) {
|
foreach (AdminAuthorizationRegistry::resources() as $resource) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\Admin\Agent;
|
||||||
|
|
||||||
|
use App\Lottery\ErrorCode;
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use App\Support\ApiMessage;
|
||||||
|
use App\Support\ApiResponse;
|
||||||
|
use App\Services\AuditLogger;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\Agent\AgentAdminUserService;
|
||||||
|
use App\Support\AdminUserApiPresenter;
|
||||||
|
use App\Support\AgentAdminUserAuthorization;
|
||||||
|
|
||||||
|
final class AgentAdminUserDestroyController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(
|
||||||
|
\Illuminate\Http\Request $request,
|
||||||
|
AdminUser $admin_user,
|
||||||
|
AgentAdminUserService $service,
|
||||||
|
): JsonResponse {
|
||||||
|
$admin = $request->lotteryAdmin();
|
||||||
|
abort_if($admin === null, 401);
|
||||||
|
|
||||||
|
$agent = $admin_user->primaryAgentNode();
|
||||||
|
if ($agent === null) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$denied = AgentAdminUserAuthorization::denyUnlessUserManageable($admin, $admin_user);
|
||||||
|
if ($denied !== null) {
|
||||||
|
return $denied;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) $admin->id === (int) $admin_user->id) {
|
||||||
|
return ApiMessage::errorResponse(
|
||||||
|
$request,
|
||||||
|
'admin.user_cannot_delete_self',
|
||||||
|
ErrorCode::ValidationFailed->value,
|
||||||
|
null,
|
||||||
|
422,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$before = AdminUserApiPresenter::listItem($admin_user);
|
||||||
|
$id = (int) $admin_user->id;
|
||||||
|
$service->destroyUnderAgent($agent, $admin_user);
|
||||||
|
|
||||||
|
AuditLogger::recordForAdmin(
|
||||||
|
$admin,
|
||||||
|
$request,
|
||||||
|
'agent',
|
||||||
|
'agent_admin_user.destroy',
|
||||||
|
'admin_user',
|
||||||
|
(string) $id,
|
||||||
|
$before,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ApiResponse::success(['deleted' => true, 'id' => $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,10 @@ use App\Services\AuditLogger;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Services\Agent\AgentAdminUserService;
|
use App\Services\Agent\AgentAdminUserService;
|
||||||
|
use App\Lottery\ErrorCode;
|
||||||
use App\Support\AdminAgentNodeAccess;
|
use App\Support\AdminAgentNodeAccess;
|
||||||
use App\Support\AdminUserApiPresenter;
|
use App\Support\AdminUserApiPresenter;
|
||||||
|
use App\Support\ApiMessage;
|
||||||
use App\Http\Requests\Admin\AgentAdminUserRoleSyncRequest;
|
use App\Http\Requests\Admin\AgentAdminUserRoleSyncRequest;
|
||||||
|
|
||||||
final class AgentAdminUserRoleSyncController extends Controller
|
final class AgentAdminUserRoleSyncController extends Controller
|
||||||
@@ -32,8 +34,14 @@ final class AgentAdminUserRoleSyncController extends Controller
|
|||||||
return $denied;
|
return $denied;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.node.manage')) {
|
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.user.manage')) {
|
||||||
return AdminAgentNodeAccess::denyUnlessCanManageParent($admin, $agent);
|
return ApiMessage::errorResponse(
|
||||||
|
$request,
|
||||||
|
'admin.agent_user_manage_denied',
|
||||||
|
ErrorCode::AdminForbidden->value,
|
||||||
|
null,
|
||||||
|
403,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$before = AdminUserApiPresenter::listItem($admin_user);
|
$before = AdminUserApiPresenter::listItem($admin_user);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ use App\Services\AuditLogger;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Services\Agent\AgentAdminUserService;
|
use App\Services\Agent\AgentAdminUserService;
|
||||||
|
use App\Lottery\ErrorCode;
|
||||||
use App\Support\AdminAgentNodeAccess;
|
use App\Support\AdminAgentNodeAccess;
|
||||||
use App\Support\AdminUserApiPresenter;
|
use App\Support\AdminUserApiPresenter;
|
||||||
|
use App\Support\ApiMessage;
|
||||||
use App\Http\Requests\Admin\AgentAdminUserStoreRequest;
|
use App\Http\Requests\Admin\AgentAdminUserStoreRequest;
|
||||||
|
|
||||||
final class AgentNodeAdminUserStoreController extends Controller
|
final class AgentNodeAdminUserStoreController extends Controller
|
||||||
@@ -27,8 +29,14 @@ final class AgentNodeAdminUserStoreController extends Controller
|
|||||||
return $denied;
|
return $denied;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.node.manage')) {
|
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.user.manage')) {
|
||||||
return AdminAgentNodeAccess::denyUnlessCanManageParent($admin, $agent_node);
|
return ApiMessage::errorResponse(
|
||||||
|
$request,
|
||||||
|
'admin.agent_user_manage_denied',
|
||||||
|
ErrorCode::AdminForbidden->value,
|
||||||
|
null,
|
||||||
|
403,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $service->createUnderAgent($agent_node, $request->validated());
|
$user = $service->createUnderAgent($agent_node, $request->validated());
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ final class AgentNodeDestroyController extends Controller
|
|||||||
return ApiMessage::errorResponse($request, 'admin.agent_node_has_users_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
|
return ApiMessage::errorResponse($request, 'admin.agent_node_has_users_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DB::table('admin_roles')->where('owner_agent_id', (int) $agent_node->id)->exists()) {
|
if ($service->hasBlockingCustomRoles($agent_node)) {
|
||||||
return ApiMessage::errorResponse($request, 'admin.agent_node_has_roles_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
|
return ApiMessage::errorResponse($request, 'admin.agent_node_has_roles_cannot_delete', ErrorCode::ValidationFailed->value, null, 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ use App\Services\AuditLogger;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Services\Agent\AgentRoleService;
|
use App\Services\Agent\AgentRoleService;
|
||||||
|
use App\Lottery\ErrorCode;
|
||||||
use App\Support\AdminAgentNodeAccess;
|
use App\Support\AdminAgentNodeAccess;
|
||||||
use App\Support\AdminRoleApiPresenter;
|
use App\Support\AdminRoleApiPresenter;
|
||||||
|
use App\Support\ApiMessage;
|
||||||
use App\Http\Requests\Admin\AgentRoleStoreRequest;
|
use App\Http\Requests\Admin\AgentRoleStoreRequest;
|
||||||
|
|
||||||
final class AgentNodeRoleStoreController extends Controller
|
final class AgentNodeRoleStoreController extends Controller
|
||||||
@@ -27,8 +29,14 @@ final class AgentNodeRoleStoreController extends Controller
|
|||||||
return $denied;
|
return $denied;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.node.manage')) {
|
if (! $admin->isSuperAdmin() && ! $admin->hasPermissionCode('agent.role.manage')) {
|
||||||
return AdminAgentNodeAccess::denyUnlessCanManageParent($admin, $agent_node);
|
return ApiMessage::errorResponse(
|
||||||
|
$request,
|
||||||
|
'admin.agent_role_manage_denied',
|
||||||
|
ErrorCode::AdminForbidden->value,
|
||||||
|
null,
|
||||||
|
403,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$role = $service->createForAgent($admin, $agent_node, $request->validated());
|
$role = $service->createForAgent($admin, $agent_node, $request->validated());
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminCurrencyStoreRequest extends FormRequest
|
final class AdminCurrencyStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminCurrencyUpdateRequest extends FormRequest
|
final class AdminCurrencyUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see \App\Http\Controllers\Api\V1\Admin\Integration\AdminIntegrationSiteConnectivityTestController
|
* @see \App\Http\Controllers\Api\V1\Admin\Integration\AdminIntegrationSiteConnectivityTestController
|
||||||
*/
|
*/
|
||||||
final class AdminIntegrationSiteConnectivityTestRequest extends FormRequest
|
final class AdminIntegrationSiteConnectivityTestRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use App\Rules\WalletApiUrlRule;
|
use App\Rules\WalletApiUrlRule;
|
||||||
|
|
||||||
final class AdminIntegrationSiteStoreRequest extends FormRequest
|
final class AdminIntegrationSiteStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use App\Rules\WalletApiUrlRule;
|
use App\Rules\WalletApiUrlRule;
|
||||||
|
|
||||||
final class AdminIntegrationSiteUpdateRequest extends FormRequest
|
final class AdminIntegrationSiteUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员登录请求。
|
* 管理员登录请求。
|
||||||
*
|
*
|
||||||
* @see LoginController
|
* @see LoginController
|
||||||
*/
|
*/
|
||||||
final class AdminLoginRequest extends FormRequest
|
final class AdminLoginRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
@@ -29,16 +29,4 @@ final class AdminLoginRequest extends FormRequest
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function attributes(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'account' => 'account',
|
|
||||||
'password' => 'password',
|
|
||||||
'captcha_key' => 'captcha_key',
|
|
||||||
'captcha_code' => 'captcha_code',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 玩家列表查询请求。
|
* 玩家列表查询请求。
|
||||||
*
|
*
|
||||||
* @see AdminPlayerIndexController
|
* @see AdminPlayerIndexController
|
||||||
*/
|
*/
|
||||||
final class AdminPlayerIndexRequest extends FormRequest
|
final class AdminPlayerIndexRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 玩家创建请求。
|
* 玩家创建请求。
|
||||||
*
|
*
|
||||||
* @see AdminPlayerStoreController
|
* @see AdminPlayerStoreController
|
||||||
*/
|
*/
|
||||||
final class AdminPlayerStoreRequest extends FormRequest
|
final class AdminPlayerStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员查看玩家注单列表请求。
|
* 管理员查看玩家注单列表请求。
|
||||||
*
|
*
|
||||||
* @see AdminPlayerTicketItemsIndexController
|
* @see AdminPlayerTicketItemsIndexController
|
||||||
*/
|
*/
|
||||||
final class AdminPlayerTicketItemsRequest extends FormRequest
|
final class AdminPlayerTicketItemsRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 玩家更新请求。
|
* 玩家更新请求。
|
||||||
*
|
*
|
||||||
* @see AdminPlayerUpdateController
|
* @see AdminPlayerUpdateController
|
||||||
*/
|
*/
|
||||||
final class AdminPlayerUpdateRequest extends FormRequest
|
final class AdminPlayerUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminReportQueryRequest extends FormRequest
|
final class AdminReportQueryRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminRolePermissionSyncRequest extends FormRequest
|
final class AdminRolePermissionSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminRoleStoreRequest extends FormRequest
|
final class AdminRoleStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminRoleUpdateRequest extends FormRequest
|
final class AdminRoleUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use App\Models\AdminUser;
|
use App\Models\AdminUser;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminSettingBatchUpdateRequest extends FormRequest
|
final class AdminSettingBatchUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminSettingIndexRequest extends FormRequest
|
final class AdminSettingIndexRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use App\Models\AdminUser;
|
use App\Models\AdminUser;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminSettingUpdateRequest extends FormRequest
|
final class AdminSettingUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员用户权限同步请求。
|
* 管理员用户权限同步请求。
|
||||||
*
|
*
|
||||||
* @see AdminUserPermissionSyncController
|
* @see AdminUserPermissionSyncController
|
||||||
*/
|
*/
|
||||||
final class AdminUserPermissionSyncRequest extends FormRequest
|
final class AdminUserPermissionSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员用户角色同步请求。
|
* 管理员用户角色同步请求。
|
||||||
*
|
*
|
||||||
* @see AdminUserRoleSyncController
|
* @see AdminUserRoleSyncController
|
||||||
*/
|
*/
|
||||||
final class AdminUserRoleSyncRequest extends FormRequest
|
final class AdminUserRoleSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员用户创建请求。
|
* 管理员用户创建请求。
|
||||||
*
|
*
|
||||||
* @see AdminUserStoreController
|
* @see AdminUserStoreController
|
||||||
*/
|
*/
|
||||||
final class AdminUserStoreRequest extends FormRequest
|
final class AdminUserStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员用户更新请求。
|
* 管理员用户更新请求。
|
||||||
*
|
*
|
||||||
* @see AdminUserUpdateController
|
* @see AdminUserUpdateController
|
||||||
*/
|
*/
|
||||||
final class AdminUserUpdateRequest extends FormRequest
|
final class AdminUserUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentAdminUserRoleSyncRequest extends FormRequest
|
final class AgentAdminUserRoleSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
final class AgentAdminUserStoreRequest extends FormRequest
|
final class AgentAdminUserStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentDelegationGrantSyncRequest extends FormRequest
|
final class AgentDelegationGrantSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentNodeStoreRequest extends FormRequest
|
final class AgentNodeStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentNodeUpdateRequest extends FormRequest
|
final class AgentNodeUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentRolePermissionSyncRequest extends FormRequest
|
final class AgentRolePermissionSyncRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use App\Models\AdminRole;
|
use App\Models\AdminRole;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
final class AgentRoleStoreRequest extends FormRequest
|
final class AgentRoleStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AgentRoleUpdateRequest extends FormRequest
|
final class AgentRoleUpdateRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
final class DashboardAnalyticsRequest extends FormRequest
|
final class DashboardAnalyticsRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use App\Services\Draw\DrawPrizeLayout;
|
use App\Services\Draw\DrawPrizeLayout;
|
||||||
|
|
||||||
final class DrawManualResultBatchStoreRequest extends FormRequest
|
final class DrawManualResultBatchStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
@@ -33,7 +33,7 @@ final class DrawManualResultBatchStoreRequest extends FormRequest
|
|||||||
sort($actual);
|
sort($actual);
|
||||||
|
|
||||||
if ($actual !== $expected) {
|
if ($actual !== $expected) {
|
||||||
$validator->errors()->add('items', 'items must contain the complete 23 draw prize slots.');
|
$validator->errors()->add('items', __('validation.exact.items_must_contain_23_slots'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class DrawReopenRequest extends FormRequest
|
final class DrawReopenRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class DrawStoreRequest extends FormRequest
|
final class DrawStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin\Jackpot;
|
namespace App\Http\Requests\Admin\Jackpot;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class AdminJackpotPoolAdjustRequest extends FormRequest
|
final class AdminJackpotPoolAdjustRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对账任务创建请求。
|
* 对账任务创建请求。
|
||||||
*
|
*
|
||||||
* @see ReconcileJobStoreController
|
* @see ReconcileJobStoreController
|
||||||
*/
|
*/
|
||||||
final class ReconcileJobStoreRequest extends FormRequest
|
final class ReconcileJobStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
/** @see ReportJobStoreController */
|
/** @see ReportJobStoreController */
|
||||||
final class ReportJobStoreRequest extends FormRequest
|
final class ReportJobStoreRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class SettlementBatchReviewRequest extends FormRequest
|
final class SettlementBatchReviewRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class SettlementPayoutAdjustmentRequest extends FormRequest
|
final class SettlementPayoutAdjustmentRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员注单列表查询请求。
|
* 管理员注单列表查询请求。
|
||||||
*
|
*
|
||||||
* @see AdminTicketItemIndexController
|
* @see AdminTicketItemIndexController
|
||||||
*/
|
*/
|
||||||
final class TicketItemListRequest extends FormRequest
|
final class TicketItemListRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转账单列表查询请求。
|
* 转账单列表查询请求。
|
||||||
*
|
*
|
||||||
* @see TransferOrderListController
|
* @see TransferOrderListController
|
||||||
*/
|
*/
|
||||||
final class TransferOrderListRequest extends FormRequest
|
final class TransferOrderListRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin\Wallet;
|
namespace App\Http\Requests\Admin\Wallet;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class TransferOrderCompleteCreditRequest extends FormRequest
|
final class TransferOrderCompleteCreditRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin\Wallet;
|
namespace App\Http\Requests\Admin\Wallet;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class TransferOrderManuallyProcessRequest extends FormRequest
|
final class TransferOrderManuallyProcessRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin\Wallet;
|
namespace App\Http\Requests\Admin\Wallet;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
final class TransferOrderReverseRequest extends FormRequest
|
final class TransferOrderReverseRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Admin;
|
namespace App\Http\Requests\Admin;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 钱包流水列表查询请求。
|
* 钱包流水列表查询请求。
|
||||||
*
|
*
|
||||||
* @see WalletTransactionListController
|
* @see WalletTransactionListController
|
||||||
*/
|
*/
|
||||||
final class WalletTransactionListRequest extends FormRequest
|
final class WalletTransactionListRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
24
app/Http/Requests/ApiFormRequest.php
Normal file
24
app/Http/Requests/ApiFormRequest.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 表单校验基类:字段中文名等从 lang/validation.php 的 attributes 读取。
|
||||||
|
*/
|
||||||
|
abstract class ApiFormRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
$locale = $this->request->attributes->get('lottery_locale')
|
||||||
|
?? config('lottery.locales.fallback', 'en');
|
||||||
|
|
||||||
|
$labels = trans('validation.attributes', [], (string) $locale);
|
||||||
|
|
||||||
|
return is_array($labels) ? $labels : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Ticket;
|
namespace App\Http\Requests\Ticket;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
abstract class TicketBetRequest extends FormRequest
|
abstract class TicketBetRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Wallet;
|
namespace App\Http\Requests\Wallet;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use App\Http\Requests\ApiFormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转入 / 转出共用请求体:最小货币单位金额、幂等键、可选币种。
|
* 转入 / 转出共用请求体:最小货币单位金额、幂等键、可选币种。
|
||||||
*/
|
*/
|
||||||
final class WalletTransferRequest extends FormRequest
|
final class WalletTransferRequest extends ApiFormRequest
|
||||||
{
|
{
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Support\AdminPermissionBridge;
|
use App\Support\AdminPermissionBridge;
|
||||||
|
use App\Support\AdminPermissionInheritance;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
final class AdminRole extends Model
|
final class AdminRole extends Model
|
||||||
@@ -106,7 +108,9 @@ final class AdminRole extends Model
|
|||||||
*/
|
*/
|
||||||
public function syncLegacyPermissionSlugs(array $slugs): void
|
public function syncLegacyPermissionSlugs(array $slugs): void
|
||||||
{
|
{
|
||||||
$legacySlugs = AdminPermissionBridge::normalizeCanonicalLegacySlugs($slugs);
|
$legacySlugs = AdminPermissionInheritance::expand(
|
||||||
|
AdminPermissionBridge::normalizeCanonicalLegacySlugs($slugs),
|
||||||
|
);
|
||||||
|
|
||||||
$codes = [];
|
$codes = [];
|
||||||
foreach ($legacySlugs as $slug) {
|
foreach ($legacySlugs as $slug) {
|
||||||
@@ -127,6 +131,17 @@ final class AdminRole extends Model
|
|||||||
'menu_action_id' => (int) $mid,
|
'menu_action_id' => (int) $mid,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$granted = $this->legacyPermissionSlugs();
|
||||||
|
$missing = array_values(array_diff($legacySlugs, $granted));
|
||||||
|
if ($missing !== []) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'permission_slugs' => [
|
||||||
|
'permission_catalog_incomplete: '.implode(', ', $missing)
|
||||||
|
.' (run: php artisan migrate && php artisan lottery:admin-auth-sync --audit)',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assignedUserCount(): int
|
public function assignedUserCount(): int
|
||||||
|
|||||||
@@ -128,7 +128,10 @@ final class AdminUser extends Authenticatable
|
|||||||
{
|
{
|
||||||
$agentId = $this->primaryAgentNodeId();
|
$agentId = $this->primaryAgentNodeId();
|
||||||
if ($agentId !== null) {
|
if ($agentId !== null) {
|
||||||
return $this->agentRoleMenuActionPermissionCodes($agentId);
|
$fromAgent = $this->agentRoleMenuActionPermissionCodes($agentId);
|
||||||
|
if ($fromAgent !== []) {
|
||||||
|
return $fromAgent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->siteRoleMenuActionPermissionCodes();
|
return $this->siteRoleMenuActionPermissionCodes();
|
||||||
@@ -197,6 +200,11 @@ final class AdminUser extends Authenticatable
|
|||||||
'granted_at' => $now,
|
'granted_at' => $now,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$agentId = $this->primaryAgentNodeId();
|
||||||
|
if ($agentId !== null) {
|
||||||
|
$this->syncAgentRoleIds($agentId, array_map('intval', $roleIds));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ final class WalletApiUrlRule implements Rule
|
|||||||
|
|
||||||
public function message(): string
|
public function message(): string
|
||||||
{
|
{
|
||||||
return 'wallet_api_url 必须是 https 的公开域名根地址,并拒绝 localhost/内网 IP 与带路径/查询的地址。';
|
return (string) __('validation.custom.wallet_api_url.wallet_api_url');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,4 +82,31 @@ final class AgentAdminUserService
|
|||||||
throw ValidationException::withMessages(['role_ids' => ['invalid_for_agent']]);
|
throw ValidationException::withMessages(['role_ids' => ['invalid_for_agent']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function destroyUnderAgent(AgentNode $agent, AdminUser $user): void
|
||||||
|
{
|
||||||
|
if ((int) $user->primaryAgentNodeId() !== (int) $agent->id) {
|
||||||
|
throw ValidationException::withMessages(['user' => ['agent_mismatch']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(static function () use ($agent, $user): void {
|
||||||
|
DB::table('admin_user_agent_roles')
|
||||||
|
->where('admin_user_id', $user->id)
|
||||||
|
->where('agent_node_id', $agent->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
DB::table('admin_user_agents')
|
||||||
|
->where('admin_user_id', $user->id)
|
||||||
|
->where('agent_node_id', $agent->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$siteId = (int) $agent->admin_site_id;
|
||||||
|
DB::table('admin_user_site_roles')
|
||||||
|
->where('admin_user_id', $user->id)
|
||||||
|
->where('site_id', $siteId)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services\Agent;
|
namespace App\Services\Agent;
|
||||||
|
|
||||||
|
use App\Models\AdminRole;
|
||||||
use App\Models\AdminUser;
|
use App\Models\AdminUser;
|
||||||
use App\Models\AgentNode;
|
use App\Models\AgentNode;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -76,7 +77,20 @@ final class AgentNodeService
|
|||||||
public function destroy(AgentNode $node): void
|
public function destroy(AgentNode $node): void
|
||||||
{
|
{
|
||||||
DB::transaction(static function () use ($node): void {
|
DB::transaction(static function () use ($node): void {
|
||||||
|
AdminRole::query()
|
||||||
|
->where('owner_agent_id', $node->id)
|
||||||
|
->whereNotNull('delegated_from_role_id')
|
||||||
|
->each(static fn (AdminRole $role): bool => (bool) $role->delete());
|
||||||
|
|
||||||
$node->delete();
|
$node->delete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasBlockingCustomRoles(AgentNode $node): bool
|
||||||
|
{
|
||||||
|
return AdminRole::query()
|
||||||
|
->where('owner_agent_id', $node->id)
|
||||||
|
->whereNull('delegated_from_role_id')
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,11 @@ final class AgentRoleService
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($role->assignedUserCount() > 0) {
|
if ($role->assignedUserCount() > 0) {
|
||||||
throw ValidationException::withMessages(['role' => ['in_use']]);
|
throw ValidationException::withMessages([
|
||||||
|
'role' => [
|
||||||
|
__('admin.agent_role_in_use', ['count' => $role->assignedUserCount()]),
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$role->delete();
|
$role->delete();
|
||||||
|
|||||||
85
app/Support/AdminAgentPermissionMenuActionSync.php
Normal file
85
app/Support/AdminAgentPermissionMenuActionSync.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保代理「角色 / 账号」拆分后的 menu_action 行存在(migrate 未跑或漏跑时 auth-sync 可补救)。
|
||||||
|
*/
|
||||||
|
final class AdminAgentPermissionMenuActionSync
|
||||||
|
{
|
||||||
|
public static function syncMissing(): int
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$viewActionId = DB::table('admin_action_catalog')->where('code', 'view')->value('id');
|
||||||
|
$manageActionId = DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
|
||||||
|
if ($viewActionId === null || $manageActionId === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agentMenuId = (int) DB::table('admin_menus')->where('code', 'system.agents')->value('id');
|
||||||
|
if ($agentMenuId === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rolesMenuId = self::ensureChildMenu($agentMenuId, 'system.agents.roles', '代理角色', $now);
|
||||||
|
$usersMenuId = self::ensureChildMenu($agentMenuId, 'system.agents.users', '代理账号', $now);
|
||||||
|
|
||||||
|
$created = 0;
|
||||||
|
$created += self::ensureMenuAction((int) $rolesMenuId, (int) $viewActionId, 'agent.role.view', '代理角色查看', $now) ? 1 : 0;
|
||||||
|
$created += self::ensureMenuAction((int) $rolesMenuId, (int) $manageActionId, 'agent.role.manage', '代理角色管理', $now) ? 1 : 0;
|
||||||
|
$created += self::ensureMenuAction((int) $usersMenuId, (int) $viewActionId, 'agent.user.view', '代理账号查看', $now) ? 1 : 0;
|
||||||
|
$created += self::ensureMenuAction((int) $usersMenuId, (int) $manageActionId, 'agent.user.manage', '代理账号管理', $now) ? 1 : 0;
|
||||||
|
|
||||||
|
return $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function ensureChildMenu(int $parentId, string $code, string $name, Carbon $now): int
|
||||||
|
{
|
||||||
|
$existing = DB::table('admin_menus')->where('code', $code)->value('id');
|
||||||
|
if ($existing !== null) {
|
||||||
|
return (int) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) DB::table('admin_menus')->insertGetId([
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'menu_type' => 'button',
|
||||||
|
'code' => $code,
|
||||||
|
'name' => $name,
|
||||||
|
'path' => null,
|
||||||
|
'route_name' => null,
|
||||||
|
'component' => null,
|
||||||
|
'icon' => null,
|
||||||
|
'active_menu_code' => 'system.agents',
|
||||||
|
'sort_order' => 0,
|
||||||
|
'is_visible' => false,
|
||||||
|
'is_cache' => false,
|
||||||
|
'is_external' => false,
|
||||||
|
'status' => 1,
|
||||||
|
'meta_json' => null,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function ensureMenuAction(int $menuId, int $actionId, string $permissionCode, string $name, Carbon $now): bool
|
||||||
|
{
|
||||||
|
if (DB::table('admin_menu_actions')->where('permission_code', $permissionCode)->exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('admin_menu_actions')->insert([
|
||||||
|
'menu_id' => $menuId,
|
||||||
|
'action_id' => $actionId,
|
||||||
|
'permission_code' => $permissionCode,
|
||||||
|
'name' => $name,
|
||||||
|
'status' => 1,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,10 +29,10 @@ final class AdminAuthorizationRegistry
|
|||||||
|
|
||||||
['slug' => 'prd.agent.view', 'name' => '代理管理·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.view']],
|
['slug' => 'prd.agent.view', 'name' => '代理管理·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.view']],
|
||||||
['slug' => 'prd.agent.manage', 'name' => '代理管理·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.manage']],
|
['slug' => 'prd.agent.manage', 'name' => '代理管理·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.manage']],
|
||||||
['slug' => 'prd.agent.role.view', 'name' => '代理角色·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.view']],
|
['slug' => 'prd.agent.role.view', 'name' => '代理角色·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.role.view', 'agent.node.view']],
|
||||||
['slug' => 'prd.agent.role.manage', 'name' => '代理角色·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.manage']],
|
['slug' => 'prd.agent.role.manage', 'name' => '代理角色·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.role.manage']],
|
||||||
['slug' => 'prd.agent.user.view', 'name' => '代理账号·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.view']],
|
['slug' => 'prd.agent.user.view', 'name' => '代理账号·查看', 'nav_segment' => 'agents', 'permission_codes' => ['agent.user.view', 'agent.node.view']],
|
||||||
['slug' => 'prd.agent.user.manage', 'name' => '代理账号·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.node.manage']],
|
['slug' => 'prd.agent.user.manage', 'name' => '代理账号·可管理', 'nav_segment' => 'agents', 'permission_codes' => ['agent.user.manage']],
|
||||||
|
|
||||||
['slug' => 'prd.users.manage', 'name' => '用户管理·可管理', 'nav_segment' => 'players', 'permission_codes' => ['service.players.manage']],
|
['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_finance', 'name' => '用户管理·财务查看', 'nav_segment' => 'players', 'permission_codes' => ['service.players.view', 'service.wallet.view']],
|
||||||
@@ -49,9 +49,9 @@ final class AdminAuthorizationRegistry
|
|||||||
['slug' => 'prd.wallet_reconcile.view_cs', '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.adjust']],
|
['slug' => 'prd.wallet_adjust.manage', 'name' => '补单/冲正·可管理', 'nav_segment' => 'wallet', 'permission_codes' => ['service.wallet.adjust']],
|
||||||
|
|
||||||
['slug' => 'prd.draw_result.manage', 'name' => '开奖结果录入·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.results.view', 'draw.review.review', 'draw.review.publish']],
|
['slug' => 'prd.draw_result.manage', 'name' => '开奖结果录入·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.review.review', 'draw.review.publish']],
|
||||||
['slug' => 'prd.draw_result.view', 'name' => '开奖结果·查看', 'nav_segment' => 'draws', 'permission_codes' => ['draw.results.view']],
|
['slug' => 'prd.draw_result.view', 'name' => '开奖结果·查看', 'nav_segment' => 'draws', 'permission_codes' => ['draw.results.view']],
|
||||||
['slug' => 'prd.draw_reopen.manage', 'name' => '开奖结果重开·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.review.publish']],
|
['slug' => 'prd.draw_reopen.manage', 'name' => '开奖结果重开·可管理', 'nav_segment' => 'draws', 'permission_codes' => ['draw.reopen.manage']],
|
||||||
|
|
||||||
['slug' => 'prd.risk.view', 'name' => '风控中心·查看', 'nav_segment' => 'risk', 'permission_codes' => ['risk.monitor.view']],
|
['slug' => 'prd.risk.view', 'name' => '风控中心·查看', 'nav_segment' => 'risk', 'permission_codes' => ['risk.monitor.view']],
|
||||||
['slug' => 'prd.risk.manage', 'name' => '风控中心·可管理', 'nav_segment' => 'risk', 'permission_codes' => ['risk.monitor.manage']],
|
['slug' => 'prd.risk.manage', 'name' => '风控中心·可管理', 'nav_segment' => 'risk', 'permission_codes' => ['risk.monitor.manage']],
|
||||||
@@ -386,22 +386,23 @@ final class AdminAuthorizationRegistry
|
|||||||
['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.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.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.agent-nodes.tree', 'module_code' => 'agent', 'name' => '代理树', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/tree', 'route_name' => 'api.v1.admin.agent-nodes.tree', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-nodes.tree', 'module_code' => 'agent', 'name' => '代理树', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/tree', 'route_name' => 'api.v1.admin.agent-nodes.tree', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage']],
|
||||||
['code' => 'admin.agent-nodes.store', 'module_code' => 'agent', 'name' => '创建下级代理', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes', 'route_name' => 'api.v1.admin.agent-nodes.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-nodes.store', 'module_code' => 'agent', 'name' => '创建下级代理', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes', 'route_name' => 'api.v1.admin.agent-nodes.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||||
['code' => 'admin.agent-nodes.show', 'module_code' => 'agent', 'name' => '代理节点详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-nodes.show', 'module_code' => 'agent', 'name' => '代理节点详情', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.show', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||||
['code' => 'admin.agent-nodes.update', 'module_code' => 'agent', 'name' => '更新代理节点', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-nodes.update', 'module_code' => 'agent', 'name' => '更新代理节点', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||||
['code' => 'admin.agent-nodes.destroy', 'module_code' => 'agent', 'name' => '删除代理节点', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-nodes.destroy', 'module_code' => 'agent', 'name' => '删除代理节点', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}', 'route_name' => 'api.v1.admin.agent-nodes.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||||
['code' => 'admin.agent-nodes.children', 'module_code' => 'agent', 'name' => '代理直属下级', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/children', 'route_name' => 'api.v1.admin.agent-nodes.children', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-nodes.children', 'module_code' => 'agent', 'name' => '代理直属下级', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/children', 'route_name' => 'api.v1.admin.agent-nodes.children', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||||
|
|
||||||
['code' => 'admin.agent-roles.index', 'module_code' => 'agent', 'name' => '代理角色列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/roles', 'route_name' => 'api.v1.admin.agent-roles.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-roles.index', 'module_code' => 'agent', 'name' => '代理角色列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/roles', 'route_name' => 'api.v1.admin.agent-roles.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.role.view', 'agent.role.manage', 'agent.node.view', 'agent.node.manage']],
|
||||||
['code' => 'admin.agent-roles.store', 'module_code' => 'agent', 'name' => '创建代理角色', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/roles', 'route_name' => 'api.v1.admin.agent-roles.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-roles.store', 'module_code' => 'agent', 'name' => '创建代理角色', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/roles', 'route_name' => 'api.v1.admin.agent-roles.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.role.manage']],
|
||||||
['code' => 'admin.agent-roles.update', 'module_code' => 'agent', 'name' => '更新代理角色', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}', 'route_name' => 'api.v1.admin.agent-roles.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-roles.update', 'module_code' => 'agent', 'name' => '更新代理角色', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}', 'route_name' => 'api.v1.admin.agent-roles.update', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.role.manage']],
|
||||||
['code' => 'admin.agent-roles.permissions.sync', 'module_code' => 'agent', 'name' => '代理角色权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}/permissions', 'route_name' => 'api.v1.admin.agent-roles.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-roles.permissions.sync', 'module_code' => 'agent', 'name' => '代理角色权限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}/permissions', 'route_name' => 'api.v1.admin.agent-roles.permissions.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.role.manage']],
|
||||||
['code' => 'admin.agent-roles.destroy', 'module_code' => 'agent', 'name' => '删除代理角色', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}', 'route_name' => 'api.v1.admin.agent-roles.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-roles.destroy', 'module_code' => 'agent', 'name' => '删除代理角色', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/agent-roles/{admin_role}', 'route_name' => 'api.v1.admin.agent-roles.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.role.manage']],
|
||||||
|
|
||||||
['code' => 'admin.agent-admin-users.index', 'module_code' => 'agent', 'name' => '代理账号列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/admin-users', 'route_name' => 'api.v1.admin.agent-admin-users.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-admin-users.index', 'module_code' => 'agent', 'name' => '代理账号列表', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/admin-users', 'route_name' => 'api.v1.admin.agent-admin-users.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.user.view', 'agent.user.manage', 'agent.node.view', 'agent.node.manage']],
|
||||||
['code' => 'admin.agent-admin-users.store', 'module_code' => 'agent', 'name' => '创建代理账号', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/admin-users', 'route_name' => 'api.v1.admin.agent-admin-users.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-admin-users.store', 'module_code' => 'agent', 'name' => '创建代理账号', 'http_method' => 'POST', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/admin-users', 'route_name' => 'api.v1.admin.agent-admin-users.store', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.user.manage']],
|
||||||
['code' => 'admin.agent-admin-users.roles.sync', 'module_code' => 'agent', 'name' => '代理账号角色同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-admin-users/{admin_user}/roles', 'route_name' => 'api.v1.admin.agent-admin-users.roles.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-admin-users.roles.sync', 'module_code' => 'agent', 'name' => '代理账号角色同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-admin-users/{admin_user}/roles', 'route_name' => 'api.v1.admin.agent-admin-users.roles.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.user.manage']],
|
||||||
|
['code' => 'admin.agent-admin-users.destroy', 'module_code' => 'agent', 'name' => '删除代理账号', 'http_method' => 'DELETE', 'uri_pattern' => '/api/v1/admin/agent-admin-users/{admin_user}', 'route_name' => 'api.v1.admin.agent-admin-users.destroy', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.user.manage']],
|
||||||
|
|
||||||
['code' => 'admin.agent-delegation-grants.index', 'module_code' => 'agent', 'name' => '代理下放上限查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
['code' => 'admin.agent-delegation-grants.index', 'module_code' => 'agent', 'name' => '代理下放上限查看', 'http_method' => 'GET', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.index', 'auth_mode' => 'permission_required', 'is_audit_required' => false, 'permission_codes' => ['agent.node.view', 'agent.node.manage']],
|
||||||
['code' => 'admin.agent-delegation-grants.sync', 'module_code' => 'agent', 'name' => '代理下放上限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
['code' => 'admin.agent-delegation-grants.sync', 'module_code' => 'agent', 'name' => '代理下放上限同步', 'http_method' => 'PUT', 'uri_pattern' => '/api/v1/admin/agent-nodes/{agent_node}/delegation-grants', 'route_name' => 'api.v1.admin.agent-delegation-grants.sync', 'auth_mode' => 'permission_required', 'is_audit_required' => true, 'permission_codes' => ['agent.node.manage']],
|
||||||
|
|||||||
36
app/Support/AdminDrawPermissionMenuActionSync.php
Normal file
36
app/Support/AdminDrawPermissionMenuActionSync.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/** 确保期号「重开」独立 permission_code 存在,避免与「管理」共用 publish 导致勾选无法拆分。 */
|
||||||
|
final class AdminDrawPermissionMenuActionSync
|
||||||
|
{
|
||||||
|
public static function syncMissing(): int
|
||||||
|
{
|
||||||
|
if (DB::table('admin_menu_actions')->where('permission_code', 'draw.reopen.manage')->exists()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$menuId = (int) DB::table('admin_menus')->where('code', 'draw.results')->value('id');
|
||||||
|
$actionId = DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
|
||||||
|
if ($menuId === 0 || $actionId === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = Carbon::now();
|
||||||
|
DB::table('admin_menu_actions')->insert([
|
||||||
|
'menu_id' => $menuId,
|
||||||
|
'action_id' => (int) $actionId,
|
||||||
|
'permission_code' => 'draw.reopen.manage',
|
||||||
|
'name' => '期号重开',
|
||||||
|
'status' => 1,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ final class AdminPermissionInheritance
|
|||||||
*/
|
*/
|
||||||
private const IMPLIED_BY_SLUG = [
|
private const IMPLIED_BY_SLUG = [
|
||||||
'prd.agent.manage' => ['prd.agent.view'],
|
'prd.agent.manage' => ['prd.agent.view'],
|
||||||
'prd.agent.role.manage' => ['prd.agent.role.view'],
|
'prd.agent.role.manage' => ['prd.agent.role.view', 'prd.agent.view'],
|
||||||
'prd.agent.user.manage' => ['prd.agent.user.view'],
|
'prd.agent.user.manage' => ['prd.agent.user.view', 'prd.agent.view'],
|
||||||
'prd.integration.manage' => ['prd.integration.view'],
|
'prd.integration.manage' => ['prd.integration.view'],
|
||||||
'prd.wallet_reconcile.manage' => ['prd.wallet_reconcile.view'],
|
'prd.wallet_reconcile.manage' => ['prd.wallet_reconcile.view'],
|
||||||
'prd.draw_result.manage' => ['prd.draw_result.view'],
|
'prd.draw_result.manage' => ['prd.draw_result.view'],
|
||||||
|
|||||||
57
app/Support/AgentAdminUserAuthorization.php
Normal file
57
app/Support/AgentAdminUserAuthorization.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use App\Models\AgentNode;
|
||||||
|
|
||||||
|
final class AgentAdminUserAuthorization
|
||||||
|
{
|
||||||
|
public static function userVisibleTo(AdminUser $admin, AdminUser $target): bool
|
||||||
|
{
|
||||||
|
if ($admin->isSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agent = $target->primaryAgentNode();
|
||||||
|
if ($agent === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AdminAgentScope::nodeVisibleTo($admin, $agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function userManageableBy(AdminUser $admin, AdminUser $target): bool
|
||||||
|
{
|
||||||
|
if (! self::userVisibleTo($admin, $target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($admin->isSuperAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $admin->hasPermissionCode('agent.user.manage')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agent = $target->primaryAgentNode();
|
||||||
|
|
||||||
|
return $agent !== null && AdminAgentScope::nodeManageableBy($admin, $agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function denyUnlessUserManageable(AdminUser $admin, AdminUser $target): ?\Illuminate\Http\JsonResponse
|
||||||
|
{
|
||||||
|
if (self::userManageableBy($admin, $target)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiMessage::errorResponse(
|
||||||
|
request(),
|
||||||
|
'admin.agent_user_manage_denied',
|
||||||
|
\App\Lottery\ErrorCode::AdminForbidden->value,
|
||||||
|
null,
|
||||||
|
403,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ final class AgentRoleAuthorization
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $admin->isSuperAdmin() || $admin->hasPermissionCode('agent.node.manage');
|
return $admin->isSuperAdmin() || $admin->hasPermissionCode('agent.role.manage');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
286
app/Support/ApiValidationErrors.php
Normal file
286
app/Support/ApiValidationErrors.php
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将校验错误转为当前请求语言下可读文案,并生成适合 toast 的摘要 msg。
|
||||||
|
*/
|
||||||
|
final class ApiValidationErrors
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string, array<int, string>|string> $errors
|
||||||
|
* @return array<string, list<string>>
|
||||||
|
*/
|
||||||
|
public static function normalize(array $errors, string $locale): array
|
||||||
|
{
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
foreach ($errors as $field => $messages) {
|
||||||
|
$fieldKey = (string) $field;
|
||||||
|
$normalized = [];
|
||||||
|
foreach ((array) $messages as $message) {
|
||||||
|
$normalized[] = self::present($fieldKey, (string) $message, $locale);
|
||||||
|
}
|
||||||
|
$out[$fieldKey] = $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, list<string>> $normalized
|
||||||
|
*/
|
||||||
|
public static function summary(array $normalized, int $maxParts = 3): ?string
|
||||||
|
{
|
||||||
|
$parts = [];
|
||||||
|
|
||||||
|
foreach ($normalized as $messages) {
|
||||||
|
foreach ($messages as $message) {
|
||||||
|
$parts[] = $message;
|
||||||
|
if (count($parts) >= $maxParts) {
|
||||||
|
return implode(';', $parts).'…';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parts === [] ? null : implode(';', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function present(string $field, string $message, string $locale): string
|
||||||
|
{
|
||||||
|
$trimmed = trim($message);
|
||||||
|
if ($trimmed === '') {
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/\p{Han}/u', $trimmed) === 1) {
|
||||||
|
return $trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exact = self::exactMessage($trimmed, $locale);
|
||||||
|
if ($exact !== null) {
|
||||||
|
return $exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match(
|
||||||
|
'/^(exceeds_actor|exceeds_parent_ceiling|exceeds_delegation_ceiling|permission_exceeds_actor|permission_catalog_incomplete)\s*:\s*(.+)$/u',
|
||||||
|
$trimmed,
|
||||||
|
$matches,
|
||||||
|
) === 1) {
|
||||||
|
$detail = trim(preg_replace('/\s*\(run:.*$/i', '', $matches[2]) ?? $matches[2]);
|
||||||
|
$line = trans('validation.business.'.$matches[1], ['detail' => $detail], $locale);
|
||||||
|
if ($line !== 'validation.business.'.$matches[1]) {
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$attribute = self::attributeLabel($field, $locale);
|
||||||
|
|
||||||
|
$businessKey = 'validation.business.'.$trimmed;
|
||||||
|
$businessLine = trans($businessKey, ['attribute' => $attribute], $locale);
|
||||||
|
if ($businessLine !== $businessKey) {
|
||||||
|
return $businessLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
$customLine = self::customRuleLine($field, $trimmed, $attribute, $locale);
|
||||||
|
if ($customLine !== null) {
|
||||||
|
return $customLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ruleLine = self::standardRuleLine($trimmed, $attribute, $locale);
|
||||||
|
if ($ruleLine !== null) {
|
||||||
|
return $ruleLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
$humanized = self::humanizeLaravelEnglish($field, $trimmed, $locale, $attribute);
|
||||||
|
if ($humanized !== null) {
|
||||||
|
return $humanized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function exactMessage(string $message, string $locale): ?string
|
||||||
|
{
|
||||||
|
$map = trans('validation.exact', [], $locale);
|
||||||
|
if (! is_array($map)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map[$message] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function customRuleLine(
|
||||||
|
string $field,
|
||||||
|
string $rule,
|
||||||
|
string $attribute,
|
||||||
|
string $locale,
|
||||||
|
): ?string {
|
||||||
|
$candidates = array_values(array_unique([
|
||||||
|
'validation.custom.'.$field.'.'.$rule,
|
||||||
|
'validation.custom.'.self::flatFieldName($field).'.'.$rule,
|
||||||
|
]));
|
||||||
|
|
||||||
|
foreach ($candidates as $key) {
|
||||||
|
$line = trans($key, ['attribute' => $attribute], $locale);
|
||||||
|
if ($line !== $key) {
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function standardRuleLine(string $rule, string $attribute, string $locale): ?string
|
||||||
|
{
|
||||||
|
if (! preg_match('/^[a-z0-9_.]+$/i', $rule)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = 'validation.'.$rule;
|
||||||
|
$line = trans($key, ['attribute' => $attribute], $locale);
|
||||||
|
|
||||||
|
return $line !== $key ? $line : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function humanizeLaravelEnglish(
|
||||||
|
string $field,
|
||||||
|
string $message,
|
||||||
|
string $locale,
|
||||||
|
string $attribute,
|
||||||
|
): ?string {
|
||||||
|
if (preg_match('/^The (.+?) field (.+)$/i', $message, $matches) !== 1) {
|
||||||
|
if (preg_match('/^The (.+?) has already been taken\.?$/i', $message, $taken) === 1) {
|
||||||
|
$attribute = self::attributeLabelFromEnglish($taken[1], $field, $locale);
|
||||||
|
|
||||||
|
return trans('validation.unique', ['attribute' => $attribute], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^The selected (.+?) is invalid\.?$/i', $message, $selected) === 1) {
|
||||||
|
$attribute = self::attributeLabelFromEnglish($selected[1], $field, $locale);
|
||||||
|
|
||||||
|
return trans('validation.exists', ['attribute' => $attribute], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$englishName = $matches[1];
|
||||||
|
$tail = $matches[2];
|
||||||
|
$attribute = self::attributeLabelFromEnglish($englishName, $field, $locale);
|
||||||
|
|
||||||
|
$tailMap = [
|
||||||
|
'is required' => 'validation.required',
|
||||||
|
'must be a valid email address' => 'validation.email',
|
||||||
|
'must be a valid UUID' => 'validation.uuid',
|
||||||
|
'must be an integer' => 'validation.integer',
|
||||||
|
'must be a string' => 'validation.string',
|
||||||
|
'must be a number' => 'validation.numeric',
|
||||||
|
'must be a valid JSON string' => 'validation.json',
|
||||||
|
'must be true or false' => 'validation.boolean',
|
||||||
|
'must be an array' => 'validation.array',
|
||||||
|
'format is invalid' => null,
|
||||||
|
'is not allowed' => 'validation.prohibited',
|
||||||
|
'must be present' => 'validation.present',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($tailMap as $suffix => $ruleKey) {
|
||||||
|
if (stripos($tail, $suffix) === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ruleKey === null) {
|
||||||
|
return self::customRuleLine($field, 'regex', $attribute, $locale)
|
||||||
|
?? trans('validation.regex', ['attribute' => $attribute], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = trans($ruleKey, ['attribute' => $attribute], $locale);
|
||||||
|
|
||||||
|
return $line !== $ruleKey ? $line : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must be at least (\d+) characters/i', $tail, $min)) {
|
||||||
|
$customKey = 'validation.custom.'.self::flatFieldName($field).'.min';
|
||||||
|
$customLine = trans($customKey, ['attribute' => $attribute, 'min' => $min[1]], $locale);
|
||||||
|
if ($customLine !== $customKey) {
|
||||||
|
return $customLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trans('validation.min.string', ['attribute' => $attribute, 'min' => $min[1]], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must not be greater than (\d+) characters/i', $tail, $max)) {
|
||||||
|
return trans('validation.max.string', ['attribute' => $attribute, 'max' => $max[1]], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must contain (\d+) items/i', $tail, $size)) {
|
||||||
|
return trans('validation.size.array', ['attribute' => $attribute, 'size' => $size[1]], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must have at least (\d+) items/i', $tail, $minItems)) {
|
||||||
|
return trans('validation.min.array', ['attribute' => $attribute, 'min' => $minItems[1]], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must not have more than (\d+) items/i', $tail, $maxItems)) {
|
||||||
|
return trans('validation.max.array', ['attribute' => $attribute, 'max' => $maxItems[1]], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must be between ([\d.]+) and ([\d.]+)/i', $tail, $between)) {
|
||||||
|
return trans('validation.between.numeric', [
|
||||||
|
'attribute' => $attribute,
|
||||||
|
'min' => $between[1],
|
||||||
|
'max' => $between[2],
|
||||||
|
], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/must match the format (.+)$/i', $tail, $format)) {
|
||||||
|
return trans('validation.date_format', [
|
||||||
|
'attribute' => $attribute,
|
||||||
|
'format' => trim($format[1]),
|
||||||
|
], $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function attributeLabelFromEnglish(string $englishName, string $field, string $locale): string
|
||||||
|
{
|
||||||
|
$normalized = strtolower(trim($englishName));
|
||||||
|
if (preg_match('/^selected (.+)$/i', $normalized, $selected) === 1) {
|
||||||
|
$normalized = $selected[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$guess = str_replace(' ', '_', $normalized);
|
||||||
|
|
||||||
|
return self::attributeLabel($guess !== '' ? $guess : $field, $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function attributeLabel(string $field, string $locale): string
|
||||||
|
{
|
||||||
|
$candidates = array_values(array_unique([
|
||||||
|
$field,
|
||||||
|
self::flatFieldName($field),
|
||||||
|
preg_replace('/\.\d+\./', '.*.', $field) ?? $field,
|
||||||
|
preg_replace('/\.\d+\./', '.', $field) ?? $field,
|
||||||
|
]));
|
||||||
|
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
$key = 'validation.attributes.'.$candidate;
|
||||||
|
$label = trans($key, [], $locale);
|
||||||
|
if ($label !== $key) {
|
||||||
|
return $label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::flatFieldName($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function flatFieldName(string $field): string
|
||||||
|
{
|
||||||
|
if (preg_match('/\.([a-zA-Z0-9_]+)$/', $field, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
use App\Lottery\ErrorCode;
|
use App\Lottery\ErrorCode;
|
||||||
use App\Support\ApiResponse;
|
use App\Support\ApiResponse;
|
||||||
|
use App\Support\ApiValidationErrors;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Support\LotteryLocale;
|
use App\Support\LotteryLocale;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
@@ -83,10 +84,15 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$loc = $locale($request);
|
||||||
|
$errors = ApiValidationErrors::normalize($e->errors(), $loc);
|
||||||
|
$msg = ApiValidationErrors::summary($errors)
|
||||||
|
?? trans('api.validation_failed', [], $loc);
|
||||||
|
|
||||||
return ApiResponse::error(
|
return ApiResponse::error(
|
||||||
trans('api.validation_failed', [], $locale($request)),
|
$msg,
|
||||||
ErrorCode::ValidationFailed->value,
|
ErrorCode::ValidationFailed->value,
|
||||||
['errors' => $e->errors()],
|
['errors' => $errors],
|
||||||
422,
|
422,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Support\AdminAuthorizationRegistry;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理「节点 / 角色 / 账号」查看与管理拆分为独立 permission_code,避免只勾一项却获得全部管理能力。
|
||||||
|
*/
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
$viewActionId = DB::table('admin_action_catalog')->where('code', 'view')->value('id');
|
||||||
|
$manageActionId = DB::table('admin_action_catalog')->where('code', 'manage')->value('id');
|
||||||
|
if ($viewActionId === null || $manageActionId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agentMenuId = (int) DB::table('admin_menus')->where('code', 'system.agents')->value('id');
|
||||||
|
if ($agentMenuId === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rolesMenuId = $this->ensureChildMenu($agentMenuId, 'system.agents.roles', '代理角色', $now);
|
||||||
|
$usersMenuId = $this->ensureChildMenu($agentMenuId, 'system.agents.users', '代理账号', $now);
|
||||||
|
|
||||||
|
$this->ensureMenuAction((int) $rolesMenuId, (int) $viewActionId, 'agent.role.view', '代理角色查看', $now);
|
||||||
|
$this->ensureMenuAction((int) $rolesMenuId, (int) $manageActionId, 'agent.role.manage', '代理角色管理', $now);
|
||||||
|
$this->ensureMenuAction((int) $usersMenuId, (int) $viewActionId, 'agent.user.view', '代理账号查看', $now);
|
||||||
|
$this->ensureMenuAction((int) $usersMenuId, (int) $manageActionId, 'agent.user.manage', '代理账号管理', $now);
|
||||||
|
|
||||||
|
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||||
|
$nodeViewId = $menuActionIds['agent.node.view'] ?? null;
|
||||||
|
$nodeManageId = $menuActionIds['agent.node.manage'] ?? null;
|
||||||
|
$roleViewId = $menuActionIds['agent.role.view'] ?? null;
|
||||||
|
$roleManageId = $menuActionIds['agent.role.manage'] ?? null;
|
||||||
|
$userViewId = $menuActionIds['agent.user.view'] ?? null;
|
||||||
|
$userManageId = $menuActionIds['agent.user.manage'] ?? null;
|
||||||
|
|
||||||
|
if ($nodeViewId !== null && $roleViewId !== null && $userViewId !== null) {
|
||||||
|
$roleIdsWithNodeView = DB::table('admin_role_menu_actions')
|
||||||
|
->where('menu_action_id', (int) $nodeViewId)
|
||||||
|
->pluck('role_id')
|
||||||
|
->unique()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
foreach ($roleIdsWithNodeView as $roleId) {
|
||||||
|
foreach ([$roleViewId, $userViewId] as $actionId) {
|
||||||
|
$this->attachRoleMenuAction((int) $roleId, (int) $actionId, $now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nodeManageId !== null && $roleManageId !== null && $userManageId !== null) {
|
||||||
|
$roleIdsWithNodeManage = DB::table('admin_role_menu_actions')
|
||||||
|
->where('menu_action_id', (int) $nodeManageId)
|
||||||
|
->pluck('role_id')
|
||||||
|
->unique()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
foreach ($roleIdsWithNodeManage as $roleId) {
|
||||||
|
foreach ([$roleManageId, $userManageId] as $actionId) {
|
||||||
|
$this->attachRoleMenuAction((int) $roleId, (int) $actionId, $now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resources = array_values(array_filter(
|
||||||
|
AdminAuthorizationRegistry::resources(),
|
||||||
|
static fn (array $resource): bool => str_starts_with((string) $resource['code'], 'admin.agent-')
|
||||||
|
));
|
||||||
|
|
||||||
|
foreach ($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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureChildMenu(int $parentId, string $code, string $name, Carbon $now): int
|
||||||
|
{
|
||||||
|
$existing = DB::table('admin_menus')->where('code', $code)->value('id');
|
||||||
|
if ($existing !== null) {
|
||||||
|
return (int) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) DB::table('admin_menus')->insertGetId([
|
||||||
|
'parent_id' => $parentId,
|
||||||
|
'menu_type' => 'button',
|
||||||
|
'code' => $code,
|
||||||
|
'name' => $name,
|
||||||
|
'path' => null,
|
||||||
|
'route_name' => null,
|
||||||
|
'component' => null,
|
||||||
|
'icon' => null,
|
||||||
|
'active_menu_code' => 'system.agents',
|
||||||
|
'sort_order' => 0,
|
||||||
|
'is_visible' => false,
|
||||||
|
'is_cache' => false,
|
||||||
|
'is_external' => false,
|
||||||
|
'status' => 1,
|
||||||
|
'meta_json' => null,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensureMenuAction(int $menuId, int $actionId, string $permissionCode, string $name, Carbon $now): void
|
||||||
|
{
|
||||||
|
if (DB::table('admin_menu_actions')->where('permission_code', $permissionCode)->exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('admin_menu_actions')->insert([
|
||||||
|
'menu_id' => $menuId,
|
||||||
|
'action_id' => $actionId,
|
||||||
|
'permission_code' => $permissionCode,
|
||||||
|
'name' => $name,
|
||||||
|
'status' => 1,
|
||||||
|
'created_at' => $now,
|
||||||
|
'updated_at' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function attachRoleMenuAction(int $roleId, int $menuActionId, Carbon $now): void
|
||||||
|
{
|
||||||
|
$exists = DB::table('admin_role_menu_actions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('menu_action_id', $menuActionId)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('admin_role_menu_actions')->insert([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'menu_action_id' => $menuActionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$codes = ['agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage'];
|
||||||
|
$actionIds = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id')->all();
|
||||||
|
|
||||||
|
if ($actionIds !== []) {
|
||||||
|
DB::table('admin_role_menu_actions')->whereIn('menu_action_id', $actionIds)->delete();
|
||||||
|
DB::table('admin_api_resource_bindings')->whereIn('menu_action_id', $actionIds)->delete();
|
||||||
|
DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('admin_menus')->whereIn('code', ['system.agents.roles', 'system.agents.users'])->delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Support\AdminAuthorizationRegistry;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/** 补齐代理账号删除 API 资源(admin.agent-admin-users.destroy)。 */
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
private const RESOURCE_CODE = 'admin.agent-admin-users.destroy';
|
||||||
|
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$resource = collect(AdminAuthorizationRegistry::resources())
|
||||||
|
->firstWhere('code', self::RESOURCE_CODE);
|
||||||
|
|
||||||
|
if ($resource === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = Carbon::now();
|
||||||
|
$menuActionIds = DB::table('admin_menu_actions')->pluck('id', 'permission_code');
|
||||||
|
|
||||||
|
$resourceId = DB::table('admin_api_resources')
|
||||||
|
->where('code', self::RESOURCE_CODE)
|
||||||
|
->value('id');
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'module_code' => $resource['module_code'],
|
||||||
|
'name' => $resource['name'],
|
||||||
|
'http_method' => $resource['http_method'],
|
||||||
|
'uri_pattern' => $resource['uri_pattern'],
|
||||||
|
'route_name' => $resource['route_name'],
|
||||||
|
'auth_mode' => $resource['auth_mode'],
|
||||||
|
'is_audit_required' => $resource['is_audit_required'],
|
||||||
|
'status' => 1,
|
||||||
|
'meta_json' => null,
|
||||||
|
'updated_at' => $now,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($resourceId === null) {
|
||||||
|
$resourceId = DB::table('admin_api_resources')->insertGetId($payload + [
|
||||||
|
'code' => self::RESOURCE_CODE,
|
||||||
|
'created_at' => $now,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
DB::table('admin_api_resources')
|
||||||
|
->where('id', (int) $resourceId)
|
||||||
|
->update($payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$resourceId = DB::table('admin_api_resources')
|
||||||
|
->where('code', self::RESOURCE_CODE)
|
||||||
|
->value('id');
|
||||||
|
|
||||||
|
if ($resourceId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('admin_api_resource_bindings')
|
||||||
|
->where('api_resource_id', (int) $resourceId)
|
||||||
|
->delete();
|
||||||
|
DB::table('admin_api_resources')->where('id', (int) $resourceId)->delete();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,6 +5,8 @@ namespace Database\Seeders;
|
|||||||
use App\Models\AdminRole;
|
use App\Models\AdminRole;
|
||||||
use App\Models\AdminUser;
|
use App\Models\AdminUser;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Support\AdminAgentPermissionMenuActionSync;
|
||||||
|
use App\Support\AdminDrawPermissionMenuActionSync;
|
||||||
use App\Support\AdminPermissionBridge;
|
use App\Support\AdminPermissionBridge;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +30,9 @@ final class AdminRbacAndUserSeeder extends Seeder
|
|||||||
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
|
AdminAgentPermissionMenuActionSync::syncMissing();
|
||||||
|
AdminDrawPermissionMenuActionSync::syncMissing();
|
||||||
|
|
||||||
$super = AdminRole::query()->updateOrCreate(
|
$super = AdminRole::query()->updateOrCreate(
|
||||||
['slug' => AdminUser::ROLE_SUPER_ADMIN],
|
['slug' => AdminUser::ROLE_SUPER_ADMIN],
|
||||||
['code' => AdminUser::ROLE_SUPER_ADMIN, 'name' => '超级管理员'],
|
['code' => AdminUser::ROLE_SUPER_ADMIN, 'name' => '超级管理员'],
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ return [
|
|||||||
'agent_node_has_children_cannot_delete' => 'This agent node has child nodes. Delete children first.',
|
'agent_node_has_children_cannot_delete' => 'This agent node has child nodes. Delete children first.',
|
||||||
'agent_node_has_users_cannot_delete' => 'This agent node still has bound admin accounts and cannot be deleted.',
|
'agent_node_has_users_cannot_delete' => 'This agent node still has bound admin accounts and cannot be deleted.',
|
||||||
'agent_node_has_roles_cannot_delete' => 'This agent node still has bound roles and cannot be deleted.',
|
'agent_node_has_roles_cannot_delete' => 'This agent node still has bound roles and cannot be deleted.',
|
||||||
|
'agent_role_in_use' => 'This role is still assigned to :count account(s). Unbind them in Accounts before deleting.',
|
||||||
|
'agent_role_read_only' => 'Read-only template roles cannot be changed or deleted.',
|
||||||
'user_cannot_delete_self' => 'Cannot delete your own account.',
|
'user_cannot_delete_self' => 'Cannot delete your own account.',
|
||||||
'user_cannot_delete_last_super_admin' => 'Cannot delete the last super admin.',
|
'user_cannot_delete_last_super_admin' => 'Cannot delete the last super admin.',
|
||||||
'super_admin_only_for_roles' => 'Only super admins can manage roles.',
|
'super_admin_only_for_roles' => 'Only super admins can manage roles.',
|
||||||
|
|||||||
11
lang/en/validation.php
Normal file
11
lang/en/validation.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array_merge(
|
||||||
|
require __DIR__.'/validation_rules.php',
|
||||||
|
[
|
||||||
|
'attributes' => require __DIR__.'/validation_attributes.php',
|
||||||
|
'custom' => require __DIR__.'/validation_custom.php',
|
||||||
|
'business' => require __DIR__.'/validation_business.php',
|
||||||
|
'exact' => require __DIR__.'/validation_exact.php',
|
||||||
|
],
|
||||||
|
);
|
||||||
62
lang/en/validation_attributes.php
Normal file
62
lang/en/validation_attributes.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'account' => 'account',
|
||||||
|
'password' => 'password',
|
||||||
|
'captcha_key' => 'captcha',
|
||||||
|
'captcha_code' => 'captcha code',
|
||||||
|
'username' => 'username',
|
||||||
|
'nickname' => 'nickname',
|
||||||
|
'name' => 'name',
|
||||||
|
'code' => 'code',
|
||||||
|
'slug' => 'slug',
|
||||||
|
'status' => 'status',
|
||||||
|
'email' => 'email',
|
||||||
|
'group' => 'settings group',
|
||||||
|
'value' => 'value',
|
||||||
|
'key' => 'key',
|
||||||
|
'items' => 'items',
|
||||||
|
'parent_id' => 'parent node',
|
||||||
|
'role_ids' => 'roles',
|
||||||
|
'role_slugs' => 'roles',
|
||||||
|
'permission_slugs' => 'permissions',
|
||||||
|
'grants' => 'delegation grants',
|
||||||
|
'child_agent_id' => 'child agent',
|
||||||
|
'role' => 'role',
|
||||||
|
'user' => 'user',
|
||||||
|
'site_code' => 'site code',
|
||||||
|
'site_player_id' => 'site player ID',
|
||||||
|
'player_id' => 'player',
|
||||||
|
'agent_node_id' => 'agent node',
|
||||||
|
'currency_code' => 'currency',
|
||||||
|
'currency' => 'currency',
|
||||||
|
'amount' => 'amount',
|
||||||
|
'amount_delta' => 'adjustment amount',
|
||||||
|
'reason' => 'reason',
|
||||||
|
'remark' => 'remark',
|
||||||
|
'draw_id' => 'draw',
|
||||||
|
'draw_no' => 'draw number',
|
||||||
|
'draw_time' => 'draw time',
|
||||||
|
'start_time' => 'start time',
|
||||||
|
'close_time' => 'close time',
|
||||||
|
'period' => 'period',
|
||||||
|
'date_from' => 'start date',
|
||||||
|
'date_to' => 'end date',
|
||||||
|
'play_code' => 'play',
|
||||||
|
'page' => 'page',
|
||||||
|
'per_page' => 'per page',
|
||||||
|
'lines' => 'bet lines',
|
||||||
|
'lines.*.number' => 'number',
|
||||||
|
'lines.*.play_code' => 'play',
|
||||||
|
'lines.*.amount' => 'stake',
|
||||||
|
'idempotent_key' => 'idempotency key',
|
||||||
|
'wallet_api_url' => 'wallet API URL',
|
||||||
|
'prize_type' => 'prize type',
|
||||||
|
'prize_index' => 'prize index',
|
||||||
|
'number_4d' => '4-digit number',
|
||||||
|
'normalized_number' => 'number',
|
||||||
|
'items.*.play_code' => 'play',
|
||||||
|
'items.*.odds_value' => 'odds',
|
||||||
|
'items.*.display_name' => 'display name',
|
||||||
|
'report_type' => 'report type',
|
||||||
|
];
|
||||||
16
lang/en/validation_business.php
Normal file
16
lang/en/validation_business.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'unique' => 'This value already exists. Please choose another.',
|
||||||
|
'required' => ':attribute is required.',
|
||||||
|
'system_role' => 'System roles cannot be deleted.',
|
||||||
|
'agent_mismatch' => 'This user does not belong to the current agent node.',
|
||||||
|
'invalid_for_agent' => 'One or more roles are not valid for this agent.',
|
||||||
|
'not_manageable' => 'You cannot manage this child agent.',
|
||||||
|
'invalid_menu_action' => 'Invalid or unknown permission item.',
|
||||||
|
'exceeds_actor' => 'These permissions exceed what you may grant: :detail',
|
||||||
|
'exceeds_parent_ceiling' => 'These permissions exceed the parent delegation ceiling: :detail',
|
||||||
|
'exceeds_delegation_ceiling' => 'These permissions exceed this node\'s delegation ceiling: :detail',
|
||||||
|
'permission_exceeds_actor' => 'These permissions exceed what you may grant: :detail',
|
||||||
|
'permission_catalog_incomplete' => 'Permission catalog is incomplete (missing: :detail). Run migrate and admin-auth-sync.',
|
||||||
|
];
|
||||||
44
lang/en/validation_custom.php
Normal file
44
lang/en/validation_custom.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => [
|
||||||
|
'regex' => 'Code may only contain letters, digits, underscores, and hyphens; site codes must start with a lowercase letter or digit.',
|
||||||
|
'unique' => 'This code is already in use.',
|
||||||
|
],
|
||||||
|
'slug' => [
|
||||||
|
'regex' => 'Slug may only contain lowercase letters, digits, underscores, and hyphens.',
|
||||||
|
'unique' => 'This slug is already in use.',
|
||||||
|
],
|
||||||
|
'account' => [
|
||||||
|
'regex' => 'Account may only contain letters, digits, dots, underscores, and hyphens.',
|
||||||
|
],
|
||||||
|
'username' => [
|
||||||
|
'regex' => 'Username may only contain letters, digits, dots, underscores, and hyphens.',
|
||||||
|
'unique' => 'This username is already in use.',
|
||||||
|
],
|
||||||
|
'draw_no' => [
|
||||||
|
'regex' => 'Draw number must match YYYYMMDD-sequence (e.g. 20260101-001).',
|
||||||
|
],
|
||||||
|
'number_4d' => [
|
||||||
|
'regex' => 'Number must be exactly 4 digits.',
|
||||||
|
],
|
||||||
|
'normalized_number' => [
|
||||||
|
'regex' => 'Number must be exactly 4 digits.',
|
||||||
|
'size' => 'Number must be exactly 4 digits.',
|
||||||
|
],
|
||||||
|
'wallet_api_url' => [
|
||||||
|
'wallet_api_url' => 'Wallet API URL must be an https public root URL (no localhost, private IP, path, or query).',
|
||||||
|
],
|
||||||
|
'amount_delta' => [
|
||||||
|
'not_in' => 'Adjustment amount cannot be zero.',
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'min' => 'Password must be at least :min characters.',
|
||||||
|
],
|
||||||
|
'reason' => [
|
||||||
|
'min' => 'Reason must be at least :min characters.',
|
||||||
|
],
|
||||||
|
'items' => [
|
||||||
|
'size' => ':attribute must contain exactly :size items.',
|
||||||
|
],
|
||||||
|
];
|
||||||
6
lang/en/validation_exact.php
Normal file
6
lang/en/validation_exact.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'items must contain the complete 23 draw prize slots.' => 'All 23 draw prize slots must be provided (first, second, third, starter, and consolation).',
|
||||||
|
'items_must_contain_23_slots' => 'All 23 draw prize slots must be provided (first, second, third, starter, and consolation).',
|
||||||
|
];
|
||||||
54
lang/en/validation_rules.php
Normal file
54
lang/en/validation_rules.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'accepted' => 'You must accept :attribute.',
|
||||||
|
'array' => ':attribute must be a list.',
|
||||||
|
'boolean' => ':attribute must be true or false.',
|
||||||
|
'confirmed' => ':attribute confirmation does not match.',
|
||||||
|
'date' => ':attribute must be a valid date.',
|
||||||
|
'date_format' => ':attribute must match the format :format.',
|
||||||
|
'email' => ':attribute must be a valid email address.',
|
||||||
|
'exists' => 'The selected :attribute is invalid or unavailable.',
|
||||||
|
'filled' => ':attribute must have a value.',
|
||||||
|
'in' => 'The selected :attribute is invalid.',
|
||||||
|
'integer' => ':attribute must be an integer.',
|
||||||
|
'json' => ':attribute must be valid JSON.',
|
||||||
|
'max' => [
|
||||||
|
'array' => ':attribute may not have more than :max items.',
|
||||||
|
'numeric' => ':attribute may not be greater than :max.',
|
||||||
|
'string' => ':attribute may not exceed :max characters.',
|
||||||
|
],
|
||||||
|
'min' => [
|
||||||
|
'array' => ':attribute must have at least :min items.',
|
||||||
|
'numeric' => ':attribute must be at least :min.',
|
||||||
|
'string' => ':attribute must be at least :min characters.',
|
||||||
|
],
|
||||||
|
'not_in' => 'The selected :attribute is invalid.',
|
||||||
|
'not_regex' => ':attribute format is invalid.',
|
||||||
|
'numeric' => ':attribute must be a number.',
|
||||||
|
'present' => ':attribute must be present.',
|
||||||
|
'prohibited' => ':attribute is not allowed.',
|
||||||
|
'regex' => ':attribute format is invalid.',
|
||||||
|
'required' => ':attribute is required.',
|
||||||
|
'required_if' => ':attribute is required when :other is :value.',
|
||||||
|
'required_with' => ':attribute is required when :values is present.',
|
||||||
|
'required_with_all' => ':attribute is required when :values are present.',
|
||||||
|
'required_without' => ':attribute is required when :values is not present.',
|
||||||
|
'same' => ':attribute must match :other.',
|
||||||
|
'size' => [
|
||||||
|
'array' => ':attribute must contain :size items.',
|
||||||
|
'numeric' => ':attribute must be :size.',
|
||||||
|
'string' => ':attribute must be :size characters.',
|
||||||
|
],
|
||||||
|
'string' => ':attribute must be text.',
|
||||||
|
'unique' => ':attribute is already taken.',
|
||||||
|
'url' => ':attribute must be a valid URL.',
|
||||||
|
'uuid' => ':attribute must be a valid UUID.',
|
||||||
|
'between' => [
|
||||||
|
'array' => ':attribute must have between :min and :max items.',
|
||||||
|
'numeric' => ':attribute must be between :min and :max.',
|
||||||
|
'string' => ':attribute must be between :min and :max characters.',
|
||||||
|
],
|
||||||
|
'after_or_equal' => ':attribute must be on or after :date.',
|
||||||
|
'distinct' => ':attribute has a duplicate value.',
|
||||||
|
];
|
||||||
@@ -23,6 +23,8 @@ return [
|
|||||||
'agent_node_has_children_cannot_delete' => 'यस एजेन्ट नोडमा चाइल्ड नोडहरू छन्, पहिले तिनीहरू हटाउनुहोस्।',
|
'agent_node_has_children_cannot_delete' => 'यस एजेन्ट नोडमा चाइल्ड नोडहरू छन्, पहिले तिनीहरू हटाउनुहोस्।',
|
||||||
'agent_node_has_users_cannot_delete' => 'यस एजेन्ट नोडमा अझै एडमिन खाता जोडिएको छ, मेटाउन मिल्दैन।',
|
'agent_node_has_users_cannot_delete' => 'यस एजेन्ट नोडमा अझै एडमिन खाता जोडिएको छ, मेटाउन मिल्दैन।',
|
||||||
'agent_node_has_roles_cannot_delete' => 'यस एजेन्ट नोडमा अझै भूमिका जोडिएको छ, मेटाउन मिल्दैन।',
|
'agent_node_has_roles_cannot_delete' => 'यस एजेन्ट नोडमा अझै भूमिका जोडिएको छ, मेटाउन मिल्दैन।',
|
||||||
|
'agent_role_in_use' => 'यो भूमिका अझै :count खातामा प्रयोगमा छ। पहिले खाता ट्याबमा हटाउनुहोस्।',
|
||||||
|
'agent_role_read_only' => 'Read-only टेम्प्लेट भूमिका मेटाउन वा सम्पादन गर्न मिल्दैन।',
|
||||||
'user_cannot_delete_self' => 'आफ्नै खाता मेटाउन मिल्दैन।',
|
'user_cannot_delete_self' => 'आफ्नै खाता मेटाउन मिल्दैन।',
|
||||||
'user_cannot_delete_last_super_admin' => 'अन्तिम सुपर एडमिन मेटाउन मिल्दैन।',
|
'user_cannot_delete_last_super_admin' => 'अन्तिम सुपर एडमिन मेटाउन मिल्दैन।',
|
||||||
'super_admin_only_for_roles' => 'भूमिका व्यवस्थापन केवल सुपर एडमिनले गर्न सक्छ।',
|
'super_admin_only_for_roles' => 'भूमिका व्यवस्थापन केवल सुपर एडमिनले गर्न सक्छ।',
|
||||||
|
|||||||
3
lang/ne/validation.php
Normal file
3
lang/ne/validation.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return require __DIR__.'/../zh/validation.php';
|
||||||
@@ -23,6 +23,8 @@ return [
|
|||||||
'agent_node_has_children_cannot_delete' => '该代理节点存在下级代理,请先清空下级后再删除。',
|
'agent_node_has_children_cannot_delete' => '该代理节点存在下级代理,请先清空下级后再删除。',
|
||||||
'agent_node_has_users_cannot_delete' => '该代理节点下仍有关联账号,不能删除。',
|
'agent_node_has_users_cannot_delete' => '该代理节点下仍有关联账号,不能删除。',
|
||||||
'agent_node_has_roles_cannot_delete' => '该代理节点下仍有关联角色,不能删除。',
|
'agent_node_has_roles_cannot_delete' => '该代理节点下仍有关联角色,不能删除。',
|
||||||
|
'agent_role_in_use' => '该角色仍有 :count 个账号在使用,请先在「账号」里解除绑定后再删除。',
|
||||||
|
'agent_role_read_only' => '只读模板角色不可删除或修改。',
|
||||||
'user_cannot_delete_self' => '不能删除当前登录账号。',
|
'user_cannot_delete_self' => '不能删除当前登录账号。',
|
||||||
'user_cannot_delete_last_super_admin' => '不能删除最后一个超级管理员。',
|
'user_cannot_delete_last_super_admin' => '不能删除最后一个超级管理员。',
|
||||||
'super_admin_only_for_roles' => '仅超级管理员可管理角色。',
|
'super_admin_only_for_roles' => '仅超级管理员可管理角色。',
|
||||||
|
|||||||
11
lang/zh/validation.php
Normal file
11
lang/zh/validation.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array_merge(
|
||||||
|
require __DIR__.'/validation_rules.php',
|
||||||
|
[
|
||||||
|
'attributes' => require __DIR__.'/validation_attributes.php',
|
||||||
|
'custom' => require __DIR__.'/validation_custom.php',
|
||||||
|
'business' => require __DIR__.'/validation_business.php',
|
||||||
|
'exact' => require __DIR__.'/validation_exact.php',
|
||||||
|
],
|
||||||
|
);
|
||||||
162
lang/zh/validation_attributes.php
Normal file
162
lang/zh/validation_attributes.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** 全站 API 校验字段中文名(FormRequest / 控制器 validate 共用) */
|
||||||
|
return [
|
||||||
|
'account' => '登录账号',
|
||||||
|
'password' => '密码',
|
||||||
|
'captcha_key' => '验证码',
|
||||||
|
'captcha_code' => '验证码',
|
||||||
|
'username' => '用户名',
|
||||||
|
'nickname' => '昵称',
|
||||||
|
'name' => '名称',
|
||||||
|
'code' => '编码',
|
||||||
|
'slug' => '标识',
|
||||||
|
'status' => '状态',
|
||||||
|
'email' => '邮箱',
|
||||||
|
'group' => '配置分组',
|
||||||
|
'value' => '配置值',
|
||||||
|
'key' => '配置键',
|
||||||
|
'items' => '配置项列表',
|
||||||
|
'parent_id' => '上级节点',
|
||||||
|
'role_ids' => '角色',
|
||||||
|
'role_ids.*' => '角色',
|
||||||
|
'role_slugs' => '角色',
|
||||||
|
'role_slugs.*' => '角色',
|
||||||
|
'permission_slugs' => '权限',
|
||||||
|
'permission_slugs.*' => '权限项',
|
||||||
|
'permissions' => '权限',
|
||||||
|
'permissions.*' => '权限项',
|
||||||
|
'grants' => '下放权限',
|
||||||
|
'grants.*.menu_action_id' => '权限项',
|
||||||
|
'grants.*.can_delegate' => '是否可继续下放',
|
||||||
|
'child_agent_id' => '下级代理',
|
||||||
|
'role' => '角色',
|
||||||
|
'user' => '账号',
|
||||||
|
'site_id' => '站点',
|
||||||
|
'site_code' => '站点编码',
|
||||||
|
'site_player_id' => '主站玩家 ID',
|
||||||
|
'player_id' => '玩家',
|
||||||
|
'player_account' => '玩家账号',
|
||||||
|
'agent_node_id' => '代理节点',
|
||||||
|
'currency_code' => '币种',
|
||||||
|
'currency' => '币种',
|
||||||
|
'amount' => '金额',
|
||||||
|
'amount_delta' => '调整金额',
|
||||||
|
'reason' => '原因',
|
||||||
|
'remark' => '备注',
|
||||||
|
'draw_id' => '期号 ID',
|
||||||
|
'draw_no' => '期号',
|
||||||
|
'draw_time' => '开奖时间',
|
||||||
|
'start_time' => '开始时间',
|
||||||
|
'close_time' => '封盘时间',
|
||||||
|
'business_date' => '业务日期',
|
||||||
|
'sequence_no' => '流水号',
|
||||||
|
'period' => '统计周期',
|
||||||
|
'date_from' => '开始日期',
|
||||||
|
'date_to' => '结束日期',
|
||||||
|
'metric' => '指标',
|
||||||
|
'play_code' => '玩法',
|
||||||
|
'keyword' => '关键词',
|
||||||
|
'default_currency' => '默认币种',
|
||||||
|
'page' => '页码',
|
||||||
|
'per_page' => '每页条数',
|
||||||
|
'size' => '每页条数',
|
||||||
|
'number' => '号码',
|
||||||
|
'start_date' => '开始日期',
|
||||||
|
'end_date' => '结束日期',
|
||||||
|
'transfer_no' => '转账单号',
|
||||||
|
'external_ref_no' => '外部参考号',
|
||||||
|
'txn_no' => '流水号',
|
||||||
|
'biz_type' => '业务类型',
|
||||||
|
'created_from' => '创建开始日期',
|
||||||
|
'created_to' => '创建结束日期',
|
||||||
|
'report_type' => '报表类型',
|
||||||
|
'export_format' => '导出格式',
|
||||||
|
'parameters' => '报表参数',
|
||||||
|
'parameters.date_from' => '开始日期',
|
||||||
|
'parameters.date_to' => '结束日期',
|
||||||
|
'parameters.player_id' => '玩家',
|
||||||
|
'parameters.play_code' => '玩法',
|
||||||
|
'parameters.operator_id' => '操作员',
|
||||||
|
'parameters.draw_id' => '期号',
|
||||||
|
'parameters.draw_no' => '期号',
|
||||||
|
'parameters.normalized_number' => '号码',
|
||||||
|
'filter_json' => '筛选条件',
|
||||||
|
'filter_json.date_from' => '开始日期',
|
||||||
|
'filter_json.date_to' => '结束日期',
|
||||||
|
'filter_json.draw_id' => '期号',
|
||||||
|
'filter_json.draw_no' => '期号',
|
||||||
|
'filter_json.normalized_number' => '号码',
|
||||||
|
'reconcile_type' => '对账类型',
|
||||||
|
'period_start' => '周期开始',
|
||||||
|
'period_end' => '周期结束',
|
||||||
|
'decimal_places' => '小数位数',
|
||||||
|
'is_enabled' => '是否启用',
|
||||||
|
'is_bettable' => '是否可下注',
|
||||||
|
'description' => '描述',
|
||||||
|
'wallet_api_url' => '钱包 API 地址',
|
||||||
|
'wallet_debit_path' => '扣款路径',
|
||||||
|
'wallet_credit_path' => '加款路径',
|
||||||
|
'wallet_balance_path' => '余额查询路径',
|
||||||
|
'wallet_timeout_seconds' => '请求超时(秒)',
|
||||||
|
'iframe_allowed_origins' => 'iframe 允许来源',
|
||||||
|
'iframe_allowed_origins.*' => 'iframe 来源',
|
||||||
|
'lottery_h5_base_url' => 'H5 基础地址',
|
||||||
|
'notes' => '备注',
|
||||||
|
'clone_from_version_id' => '克隆来源版本',
|
||||||
|
'current_amount' => '当前金额',
|
||||||
|
'contribution_rate' => '贡献比例',
|
||||||
|
'trigger_threshold' => '触发阈值',
|
||||||
|
'payout_rate' => '派彩比例',
|
||||||
|
'force_trigger_draw_gap' => '强制触发期数间隔',
|
||||||
|
'min_bet_amount' => '最小下注额',
|
||||||
|
'combo_trigger_play_codes' => '组合触发玩法',
|
||||||
|
'combo_trigger_play_codes.*' => '组合触发玩法',
|
||||||
|
'lines' => '注单明细',
|
||||||
|
'lines.*.number' => '投注号码',
|
||||||
|
'lines.*.play_code' => '玩法',
|
||||||
|
'lines.*.amount' => '下注金额',
|
||||||
|
'lines.*.digit_slot' => '数位',
|
||||||
|
'lines.*.dimension' => '维度',
|
||||||
|
'client_trace_id' => '客户端追踪 ID',
|
||||||
|
'idempotent_key' => '幂等键',
|
||||||
|
'expected_config_versions' => '期望配置版本',
|
||||||
|
'expected_config_versions.play_config_version_no' => '玩法配置版本号',
|
||||||
|
'expected_config_versions.odds_version_no' => '赔率配置版本号',
|
||||||
|
'expected_config_versions.risk_cap_version_no' => '封顶配置版本号',
|
||||||
|
'prize_type' => '奖项类型',
|
||||||
|
'prize_index' => '奖项序号',
|
||||||
|
'number_4d' => '四位号码',
|
||||||
|
'normalized_number' => '号码',
|
||||||
|
'items.*.key' => '配置键',
|
||||||
|
'items.*.value' => '配置值',
|
||||||
|
'items.*.play_code' => '玩法',
|
||||||
|
'items.*.prize_scope' => '奖项档位',
|
||||||
|
'items.*.odds_value' => '赔率',
|
||||||
|
'items.*.rebate_rate' => '返水比例',
|
||||||
|
'items.*.commission_rate' => '佣金比例',
|
||||||
|
'items.*.currency_code' => '币种',
|
||||||
|
'items.*.category' => '分类',
|
||||||
|
'items.*.dimension' => '维度',
|
||||||
|
'items.*.bet_mode' => '下注模式',
|
||||||
|
'items.*.display_name' => '显示名称',
|
||||||
|
'items.*.display_order' => '排序',
|
||||||
|
'items.*.min_bet_amount' => '最小下注额',
|
||||||
|
'items.*.max_bet_amount' => '最大下注额',
|
||||||
|
'items.*.supports_multi_number' => '是否支持多号',
|
||||||
|
'items.*.cap_amount' => '封顶金额',
|
||||||
|
'items.*.cap_type' => '封顶类型',
|
||||||
|
'items.*.draw_id' => '期号',
|
||||||
|
'items.*.prize_type' => '奖项类型',
|
||||||
|
'items.*.prize_index' => '奖项序号',
|
||||||
|
'items.*.number_4d' => '四位号码',
|
||||||
|
'items.*.normalized_number' => '号码',
|
||||||
|
'items.*.side_a_ref' => '对账侧 A 参考',
|
||||||
|
'items.*.side_b_ref' => '对账侧 B 参考',
|
||||||
|
'items.*.difference_amount' => '差异金额',
|
||||||
|
'items.*.status' => '状态',
|
||||||
|
'sort_order' => '排序',
|
||||||
|
'supports_multi_number' => '是否支持多号',
|
||||||
|
'reserved_rule_json' => '预留规则',
|
||||||
|
'extra_config_json' => '扩展配置',
|
||||||
|
];
|
||||||
17
lang/zh/validation_business.php
Normal file
17
lang/zh/validation_business.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** 业务层 ValidationException 简写键 */
|
||||||
|
return [
|
||||||
|
'unique' => '该内容已存在,请更换后重试。',
|
||||||
|
'required' => ':attribute 不能为空。',
|
||||||
|
'system_role' => '系统角色不可删除。',
|
||||||
|
'agent_mismatch' => '该账号不属于当前代理节点。',
|
||||||
|
'invalid_for_agent' => '所选角色与当前代理不匹配。',
|
||||||
|
'not_manageable' => '无权管理该下级代理。',
|
||||||
|
'invalid_menu_action' => '权限项无效或不存在。',
|
||||||
|
'exceeds_actor' => '下列权限超出您可分配的范围::detail',
|
||||||
|
'exceeds_parent_ceiling' => '下列权限超出上级允许下放的范围::detail',
|
||||||
|
'exceeds_delegation_ceiling' => '下列权限超出本节点下放上限::detail',
|
||||||
|
'permission_exceeds_actor' => '下列权限超出您可分配的范围::detail',
|
||||||
|
'permission_catalog_incomplete' => '权限目录不完整,缺少::detail。请联系管理员执行 migrate 与 admin-auth-sync。',
|
||||||
|
];
|
||||||
60
lang/zh/validation_custom.php
Normal file
60
lang/zh/validation_custom.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** 按字段定制的格式/唯一性说明 */
|
||||||
|
return [
|
||||||
|
'code' => [
|
||||||
|
'regex' => '编码只能使用字母、数字、下划线(_)和连字符(-);站点编码须以小写字母或数字开头。',
|
||||||
|
'unique' => '该编码已被占用,请更换。',
|
||||||
|
],
|
||||||
|
'slug' => [
|
||||||
|
'regex' => '标识只能使用小写字母、数字、下划线和连字符。',
|
||||||
|
'unique' => '该标识已被占用,请更换。',
|
||||||
|
],
|
||||||
|
'account' => [
|
||||||
|
'regex' => '登录账号只能使用字母、数字、点(.)、下划线和连字符。',
|
||||||
|
],
|
||||||
|
'username' => [
|
||||||
|
'regex' => '用户名只能使用字母、数字、点(.)、下划线和连字符。',
|
||||||
|
'unique' => '该用户名已被占用,请更换。',
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'unique' => '该邮箱已被占用,请更换。',
|
||||||
|
],
|
||||||
|
'draw_no' => [
|
||||||
|
'regex' => '期号格式须为 YYYYMMDD-流水号(例如 20260101-001)。',
|
||||||
|
],
|
||||||
|
'number_4d' => [
|
||||||
|
'regex' => '号码必须是 4 位数字。',
|
||||||
|
],
|
||||||
|
'normalized_number' => [
|
||||||
|
'regex' => '号码必须是 4 位数字。',
|
||||||
|
'size' => '号码必须是 4 位数字。',
|
||||||
|
],
|
||||||
|
'wallet_api_url' => [
|
||||||
|
'wallet_api_url' => '钱包 API 地址必须是 https 的公开域名根地址,不能为 localhost、内网 IP,且不能带路径或查询参数。',
|
||||||
|
],
|
||||||
|
'amount_delta' => [
|
||||||
|
'not_in' => '调整金额不能为 0。',
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'min' => '密码至少需要 :min 个字符。',
|
||||||
|
],
|
||||||
|
'reason' => [
|
||||||
|
'min' => '原因至少需要 :min 个字符。',
|
||||||
|
],
|
||||||
|
'lines' => [
|
||||||
|
'max' => '单次最多提交 :max 注。',
|
||||||
|
'min' => '至少需要提交 :min 注。',
|
||||||
|
],
|
||||||
|
'items' => [
|
||||||
|
'min' => '至少需要 :min 条配置。',
|
||||||
|
'max' => '最多只能提交 :max 条配置。',
|
||||||
|
'size' => '必须恰好包含 :size 条配置。',
|
||||||
|
],
|
||||||
|
'role_slugs' => [
|
||||||
|
'min' => '至少须选择一个角色。',
|
||||||
|
],
|
||||||
|
'role_ids' => [
|
||||||
|
'required' => '请选择角色。',
|
||||||
|
],
|
||||||
|
];
|
||||||
7
lang/zh/validation_exact.php
Normal file
7
lang/zh/validation_exact.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** 代码中写死的英文校验句 → 中文(按原文精确匹配) */
|
||||||
|
return [
|
||||||
|
'items must contain the complete 23 draw prize slots.' => '须提交完整的 23 个开奖奖项(头奖、二奖、三奖、入围、安慰奖各档位)。',
|
||||||
|
'items_must_contain_23_slots' => '须提交完整的 23 个开奖奖项(头奖、二奖、三奖、入围、安慰奖各档位)。',
|
||||||
|
];
|
||||||
113
lang/zh/validation_rules.php
Normal file
113
lang/zh/validation_rules.php
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** Laravel 标准校验规则中文文案 */
|
||||||
|
return [
|
||||||
|
'accepted' => '请勾选接受 :attribute。',
|
||||||
|
'accepted_if' => '当 :other 为 :value 时,必须接受 :attribute。',
|
||||||
|
'active_url' => ':attribute 必须是有效的网址。',
|
||||||
|
'after' => ':attribute 必须晚于 :date。',
|
||||||
|
'after_or_equal' => ':attribute 必须不早于 :date。',
|
||||||
|
'alpha' => ':attribute 只能包含字母。',
|
||||||
|
'alpha_dash' => ':attribute 只能包含字母、数字、下划线和连字符。',
|
||||||
|
'alpha_num' => ':attribute 只能包含字母和数字。',
|
||||||
|
'array' => ':attribute 必须是列表。',
|
||||||
|
'ascii' => ':attribute 只能包含半角字母、数字和符号。',
|
||||||
|
'before' => ':attribute 必须早于 :date。',
|
||||||
|
'before_or_equal' => ':attribute 必须不晚于 :date。',
|
||||||
|
'between' => [
|
||||||
|
'array' => ':attribute 项数须在 :min 到 :max 之间。',
|
||||||
|
'file' => ':attribute 大小须在 :min 到 :max KB 之间。',
|
||||||
|
'numeric' => ':attribute 须在 :min 到 :max 之间。',
|
||||||
|
'string' => ':attribute 长度须在 :min 到 :max 个字符之间。',
|
||||||
|
],
|
||||||
|
'boolean' => ':attribute 必须是是或否。',
|
||||||
|
'confirmed' => ':attribute 两次输入不一致。',
|
||||||
|
'date' => ':attribute 必须是有效日期。',
|
||||||
|
'date_equals' => ':attribute 必须等于 :date。',
|
||||||
|
'date_format' => ':attribute 格式须为 :format。',
|
||||||
|
'decimal' => ':attribute 须保留 :decimal 位小数。',
|
||||||
|
'declined' => '必须拒绝 :attribute。',
|
||||||
|
'different' => ':attribute 与 :other 不能相同。',
|
||||||
|
'digits' => ':attribute 必须是 :digits 位数字。',
|
||||||
|
'digits_between' => ':attribute 须在 :min 到 :max 位数字之间。',
|
||||||
|
'distinct' => ':attribute 存在重复值。',
|
||||||
|
'email' => ':attribute 必须是有效的邮箱地址。',
|
||||||
|
'ends_with' => ':attribute 必须以以下之一结尾::values。',
|
||||||
|
'enum' => '所选的 :attribute 无效。',
|
||||||
|
'exists' => '所选的 :attribute 不存在或不可用。',
|
||||||
|
'file' => ':attribute 必须是文件。',
|
||||||
|
'filled' => ':attribute 不能为空。',
|
||||||
|
'gt' => [
|
||||||
|
'array' => ':attribute 项数须多于 :value 个。',
|
||||||
|
'file' => ':attribute 须大于 :value KB。',
|
||||||
|
'numeric' => ':attribute 须大于 :value。',
|
||||||
|
'string' => ':attribute 须多于 :value 个字符。',
|
||||||
|
],
|
||||||
|
'gte' => [
|
||||||
|
'array' => ':attribute 至少须有 :value 项。',
|
||||||
|
'file' => ':attribute 须不小于 :value KB。',
|
||||||
|
'numeric' => ':attribute 须不小于 :value。',
|
||||||
|
'string' => ':attribute 至少须 :value 个字符。',
|
||||||
|
],
|
||||||
|
'image' => ':attribute 必须是图片。',
|
||||||
|
'in' => ':attribute 取值无效。',
|
||||||
|
'in_array' => ':attribute 不存在于 :other 中。',
|
||||||
|
'integer' => ':attribute 必须是整数。',
|
||||||
|
'ip' => ':attribute 必须是有效的 IP 地址。',
|
||||||
|
'json' => ':attribute 必须是有效的 JSON。',
|
||||||
|
'lowercase' => ':attribute 必须是小写。',
|
||||||
|
'lt' => [
|
||||||
|
'array' => ':attribute 项数须少于 :value 个。',
|
||||||
|
'file' => ':attribute 须小于 :value KB。',
|
||||||
|
'numeric' => ':attribute 须小于 :value。',
|
||||||
|
'string' => ':attribute 须少于 :value 个字符。',
|
||||||
|
],
|
||||||
|
'lte' => [
|
||||||
|
'array' => ':attribute 最多 :value 项。',
|
||||||
|
'file' => ':attribute 须不大于 :value KB。',
|
||||||
|
'numeric' => ':attribute 须不大于 :value。',
|
||||||
|
'string' => ':attribute 最多 :value 个字符。',
|
||||||
|
],
|
||||||
|
'max' => [
|
||||||
|
'array' => ':attribute 最多 :max 项。',
|
||||||
|
'file' => ':attribute 不能超过 :max KB。',
|
||||||
|
'numeric' => ':attribute 不能大于 :max。',
|
||||||
|
'string' => ':attribute 不能超过 :max 个字符。',
|
||||||
|
],
|
||||||
|
'min' => [
|
||||||
|
'array' => ':attribute 至少 :min 项。',
|
||||||
|
'file' => ':attribute 至少 :min KB。',
|
||||||
|
'numeric' => ':attribute 不能小于 :min。',
|
||||||
|
'string' => ':attribute 至少 :min 个字符。',
|
||||||
|
],
|
||||||
|
'not_in' => ':attribute 取值无效。',
|
||||||
|
'not_regex' => ':attribute 格式不正确。',
|
||||||
|
'numeric' => ':attribute 必须是数字。',
|
||||||
|
'present' => ':attribute 必须提交(可为空)。',
|
||||||
|
'prohibited' => ':attribute 不允许提交。',
|
||||||
|
'prohibited_if' => '当 :other 为 :value 时,不允许提交 :attribute。',
|
||||||
|
'regex' => ':attribute 格式不正确。',
|
||||||
|
'required' => ':attribute 不能为空。',
|
||||||
|
'required_if' => '当 :other 为 :value 时,:attribute 不能为空。',
|
||||||
|
'required_unless' => '除非 :other 在 :values 中,否则 :attribute 不能为空。',
|
||||||
|
'required_with' => '当存在 :values 时,:attribute 不能为空。',
|
||||||
|
'required_with_all' => '当存在 :values 时,:attribute 不能为空。',
|
||||||
|
'required_without' => '当不存在 :values 时,:attribute 不能为空。',
|
||||||
|
'required_without_all' => '当 :values 均不存在时,:attribute 不能为空。',
|
||||||
|
'required_array_keys' => ':attribute 须包含::values。',
|
||||||
|
'same' => ':attribute 必须与 :other 一致。',
|
||||||
|
'size' => [
|
||||||
|
'array' => ':attribute 必须包含 :size 项。',
|
||||||
|
'file' => ':attribute 必须为 :size KB。',
|
||||||
|
'numeric' => ':attribute 必须为 :size。',
|
||||||
|
'string' => ':attribute 必须为 :size 个字符。',
|
||||||
|
],
|
||||||
|
'starts_with' => ':attribute 必须以以下之一开头::values。',
|
||||||
|
'string' => ':attribute 必须是文本。',
|
||||||
|
'timezone' => ':attribute 必须是有效的时区。',
|
||||||
|
'unique' => ':attribute 已被占用,请更换。',
|
||||||
|
'uploaded' => ':attribute 上传失败。',
|
||||||
|
'uppercase' => ':attribute 必须是大写。',
|
||||||
|
'url' => ':attribute 必须是有效的网址。',
|
||||||
|
'uuid' => ':attribute 必须是有效的 UUID。',
|
||||||
|
];
|
||||||
@@ -15,6 +15,7 @@ use App\Http\Controllers\Api\V1\Admin\Agent\AgentRoleDestroyController;
|
|||||||
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeAdminUserIndexController;
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeAdminUserIndexController;
|
||||||
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeAdminUserStoreController;
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeAdminUserStoreController;
|
||||||
use App\Http\Controllers\Api\V1\Admin\Agent\AgentAdminUserRoleSyncController;
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentAdminUserRoleSyncController;
|
||||||
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentAdminUserDestroyController;
|
||||||
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeDelegationGrantIndexController;
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeDelegationGrantIndexController;
|
||||||
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeDelegationGrantSyncController;
|
use App\Http\Controllers\Api\V1\Admin\Agent\AgentNodeDelegationGrantSyncController;
|
||||||
|
|
||||||
@@ -54,4 +55,6 @@ Route::middleware('admin.api-resource')
|
|||||||
|
|
||||||
Route::put('agent-admin-users/{admin_user}/roles', AgentAdminUserRoleSyncController::class)
|
Route::put('agent-admin-users/{admin_user}/roles', AgentAdminUserRoleSyncController::class)
|
||||||
->name('api.v1.admin.agent-admin-users.roles.sync');
|
->name('api.v1.admin.agent-admin-users.roles.sync');
|
||||||
|
Route::delete('agent-admin-users/{admin_user}', AgentAdminUserDestroyController::class)
|
||||||
|
->name('api.v1.admin.agent-admin-users.destroy');
|
||||||
});
|
});
|
||||||
|
|||||||
126
tests/Feature/AdminAgentAccountRoleDestroyTest.php
Normal file
126
tests/Feature/AdminAgentAccountRoleDestroyTest.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\AdminRole;
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('agent role can be deleted when no users assigned', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'destroy_role_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
$token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$branch = app(\App\Services\Agent\AgentNodeService::class)->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'destroy-role-branch',
|
||||||
|
'name' => 'Destroy Role Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'deletable_role',
|
||||||
|
'name' => 'Deletable',
|
||||||
|
'scope_type' => AdminRole::SCOPE_AGENT,
|
||||||
|
'owner_agent_id' => $branch->id,
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.role.view']);
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->deleteJson('/api/v1/admin/agent-roles/'.$role->id)
|
||||||
|
->assertOk();
|
||||||
|
|
||||||
|
expect(AdminRole::query()->find($role->id))->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('agent role cannot be deleted while assigned to a user', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'destroy_role_blocked_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
$token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$branch = app(\App\Services\Agent\AgentNodeService::class)->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'destroy-role-blocked',
|
||||||
|
'name' => 'Blocked Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'blocked_role',
|
||||||
|
'name' => 'Blocked',
|
||||||
|
'scope_type' => AdminRole::SCOPE_AGENT,
|
||||||
|
'owner_agent_id' => $branch->id,
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.role.view']);
|
||||||
|
|
||||||
|
$user = app(\App\Services\Agent\AgentAdminUserService::class)->createUnderAgent($branch, [
|
||||||
|
'username' => 'blocked_user',
|
||||||
|
'nickname' => 'Blocked User',
|
||||||
|
'password' => 'secret-strong-2',
|
||||||
|
'role_ids' => [(int) $role->id],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($user->id)->toBeGreaterThan(0);
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->deleteJson('/api/v1/admin/agent-roles/'.$role->id)
|
||||||
|
->assertStatus(422);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('agent admin user can be deleted under agent node', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'destroy_user_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
$token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$branch = app(\App\Services\Agent\AgentNodeService::class)->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'destroy-user-branch',
|
||||||
|
'name' => 'Destroy User Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = app(\App\Services\Agent\AgentAdminUserService::class)->createUnderAgent($branch, [
|
||||||
|
'username' => 'agent_delete_me',
|
||||||
|
'nickname' => 'Delete Me',
|
||||||
|
'password' => 'secret-strong-3',
|
||||||
|
'role_ids' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->deleteJson('/api/v1/admin/agent-admin-users/'.$user->id)
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.deleted', true);
|
||||||
|
|
||||||
|
expect(AdminUser::query()->find($user->id))->toBeNull();
|
||||||
|
expect(DB::table('admin_user_agents')->where('admin_user_id', $user->id)->exists())->toBeFalse();
|
||||||
|
});
|
||||||
@@ -28,7 +28,14 @@ function grantDelegationAgentOperator(AdminUser $admin, AgentNode $agent, bool $
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$codes = $manage
|
$codes = $manage
|
||||||
? ['agent.node.view', 'agent.node.manage']
|
? [
|
||||||
|
'agent.node.view',
|
||||||
|
'agent.node.manage',
|
||||||
|
'agent.role.view',
|
||||||
|
'agent.role.manage',
|
||||||
|
'agent.user.view',
|
||||||
|
'agent.user.manage',
|
||||||
|
]
|
||||||
: ['agent.node.view'];
|
: ['agent.node.view'];
|
||||||
|
|
||||||
$actionIds = DB::table('admin_menu_actions')
|
$actionIds = DB::table('admin_menu_actions')
|
||||||
@@ -55,6 +62,13 @@ function grantDelegationAgentOperator(AdminUser $admin, AgentNode $agent, bool $
|
|||||||
'is_primary' => true,
|
'is_primary' => true,
|
||||||
'granted_at' => $now,
|
'granted_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
DB::table('admin_user_agent_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => (int) $agent->id,
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'granted_at' => $now,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('parent agent can sync delegation grants for direct child', function (): void {
|
test('parent agent can sync delegation grants for direct child', function (): void {
|
||||||
|
|||||||
@@ -36,7 +36,14 @@ function grantAgentOperatorRole(AdminUser $admin, AgentNode $agent, bool $manage
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$codes = $manage
|
$codes = $manage
|
||||||
? ['agent.node.view', 'agent.node.manage']
|
? [
|
||||||
|
'agent.node.view',
|
||||||
|
'agent.node.manage',
|
||||||
|
'agent.role.view',
|
||||||
|
'agent.role.manage',
|
||||||
|
'agent.user.view',
|
||||||
|
'agent.user.manage',
|
||||||
|
]
|
||||||
: ['agent.node.view'];
|
: ['agent.node.view'];
|
||||||
|
|
||||||
$actionIds = DB::table('admin_menu_actions')
|
$actionIds = DB::table('admin_menu_actions')
|
||||||
@@ -63,6 +70,13 @@ function grantAgentOperatorRole(AdminUser $admin, AgentNode $agent, bool $manage
|
|||||||
'is_primary' => true,
|
'is_primary' => true,
|
||||||
'granted_at' => $now,
|
'granted_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
DB::table('admin_user_agent_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => (int) $agent->id,
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'granted_at' => $now,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
test('each admin site has exactly one root agent node after migration', function (): void {
|
test('each admin site has exactly one root agent node after migration', function (): void {
|
||||||
|
|||||||
244
tests/Feature/AdminAgentPermissionGranularityTest.php
Normal file
244
tests/Feature/AdminAgentPermissionGranularityTest.php
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\AdminRole;
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use App\Support\AdminAuthProfile;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role with only agent role view slug does not receive manage slugs in profile', function (): void {
|
||||||
|
$admin = AdminUser::query()->create([
|
||||||
|
'username' => 'agent_role_view_only',
|
||||||
|
'name' => 'View Only',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_role_view_only',
|
||||||
|
'name' => 'Agent role view only',
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.role.view']);
|
||||||
|
|
||||||
|
$siteId = AdminUser::defaultAdminSiteId();
|
||||||
|
$admin->roles()->sync([
|
||||||
|
(int) $role->id => ['site_id' => $siteId, 'granted_at' => now()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = AdminAuthProfile::fromAdmin($admin->fresh());
|
||||||
|
|
||||||
|
expect($profile['permissions'])->toContain('prd.agent.role.view')
|
||||||
|
->and($profile['permissions'])->toContain('prd.agent.view')
|
||||||
|
->not->toContain('prd.agent.role.manage')
|
||||||
|
->not->toContain('prd.agent.user.manage')
|
||||||
|
->not->toContain('prd.agent.manage');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role with only agent role manage can create roles but not agent nodes', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'granularity_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
|
||||||
|
$service = app(\App\Services\Agent\AgentNodeService::class);
|
||||||
|
$branch = $service->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'granularity-branch',
|
||||||
|
'name' => 'Granularity Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin = AdminUser::query()->create([
|
||||||
|
'username' => 'agent_role_manage_only',
|
||||||
|
'name' => 'Role Manage',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_role_manage_only',
|
||||||
|
'name' => 'Agent role manage only',
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.role.manage']);
|
||||||
|
|
||||||
|
$siteId = (int) $branch->admin_site_id;
|
||||||
|
DB::table('admin_user_site_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'site_id' => $siteId,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('admin_user_agents')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => $branch->id,
|
||||||
|
'is_primary' => true,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('admin_user_agent_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => $branch->id,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [
|
||||||
|
'slug' => 'ops_role_only',
|
||||||
|
'name' => 'Ops Role',
|
||||||
|
'permission_slugs' => ['prd.agent.role.view'],
|
||||||
|
])
|
||||||
|
->assertOk();
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->postJson('/api/v1/admin/agent-nodes', [
|
||||||
|
'parent_id' => $branch->id,
|
||||||
|
'code' => 'should-fail',
|
||||||
|
'name' => 'Should Fail',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role with only agent node manage cannot create roles or admin users', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'granularity_super_node',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
|
||||||
|
$service = app(\App\Services\Agent\AgentNodeService::class);
|
||||||
|
$branch = $service->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'granularity-node-only',
|
||||||
|
'name' => 'Node Only Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$admin = AdminUser::query()->create([
|
||||||
|
'username' => 'agent_node_manage_only',
|
||||||
|
'name' => 'Node Manage',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_node_manage_only',
|
||||||
|
'name' => 'Agent node manage only',
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.manage']);
|
||||||
|
|
||||||
|
$siteId = (int) $branch->admin_site_id;
|
||||||
|
DB::table('admin_user_site_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'site_id' => $siteId,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('admin_user_agents')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => $branch->id,
|
||||||
|
'is_primary' => true,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('admin_user_agent_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => $branch->id,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [
|
||||||
|
'slug' => 'should_fail_role',
|
||||||
|
'name' => 'Should Fail Role',
|
||||||
|
'permission_slugs' => ['prd.agent.role.view'],
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/admin-users', [
|
||||||
|
'username' => 'should_fail_user',
|
||||||
|
'name' => 'Should Fail User',
|
||||||
|
'password' => 'secret-strong-2',
|
||||||
|
'role_ids' => [],
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('agent-bound admin with only site role bindings still receives manage slugs in profile', function (): void {
|
||||||
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
||||||
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
||||||
|
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'granularity_super_site_fallback',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
|
||||||
|
$service = app(\App\Services\Agent\AgentNodeService::class);
|
||||||
|
$branch = $service->createChild($super, [
|
||||||
|
'parent_id' => $rootId,
|
||||||
|
'code' => 'site-fallback-branch',
|
||||||
|
'name' => 'Site Fallback Branch',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_role_manage_site_only',
|
||||||
|
'name' => 'Agent role manage site bind',
|
||||||
|
'scope_type' => \App\Models\AdminRole::SCOPE_AGENT,
|
||||||
|
'owner_agent_id' => $branch->id,
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.agent.role.manage']);
|
||||||
|
|
||||||
|
$admin = AdminUser::query()->create([
|
||||||
|
'username' => 'agent_site_roles_only',
|
||||||
|
'name' => 'Site Roles Only',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::table('admin_user_agents')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'agent_node_id' => $branch->id,
|
||||||
|
'is_primary' => true,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('admin_user_site_roles')->insert([
|
||||||
|
'admin_user_id' => $admin->id,
|
||||||
|
'site_id' => $siteId,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
'granted_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = AdminAuthProfile::fromAdmin($admin->fresh());
|
||||||
|
|
||||||
|
expect($profile['permissions'])->toContain('prd.agent.role.manage');
|
||||||
|
expect(DB::table('admin_user_agent_roles')->where('admin_user_id', $admin->id)->count())->toBe(0);
|
||||||
|
});
|
||||||
@@ -28,7 +28,15 @@ function grantAgentRoleManager(AdminUser $admin, AgentNode $agent): void
|
|||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$codes = ['agent.node.view', 'agent.node.manage', 'service.players.view'];
|
$codes = [
|
||||||
|
'agent.node.view',
|
||||||
|
'agent.node.manage',
|
||||||
|
'agent.role.view',
|
||||||
|
'agent.role.manage',
|
||||||
|
'agent.user.view',
|
||||||
|
'agent.user.manage',
|
||||||
|
'service.players.view',
|
||||||
|
];
|
||||||
$actionIds = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id');
|
$actionIds = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id');
|
||||||
foreach ($actionIds as $actionId) {
|
foreach ($actionIds as $actionId) {
|
||||||
DB::table('admin_role_menu_actions')->insert([
|
DB::table('admin_role_menu_actions')->insert([
|
||||||
|
|||||||
62
tests/Feature/AdminAgentRolePermissionPersistTest.php
Normal file
62
tests/Feature/AdminAgentRolePermissionPersistTest.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\AdminRole;
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform role sync persists agent role and user manage slugs', function (): void {
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'persist_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
$token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_persist_test',
|
||||||
|
'name' => 'Agent Persist Test',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->putJson('/api/v1/admin/admin-roles/'.$role->id.'/permissions', [
|
||||||
|
'permission_slugs' => [
|
||||||
|
'prd.agent.view',
|
||||||
|
'prd.agent.manage',
|
||||||
|
'prd.agent.role.view',
|
||||||
|
'prd.agent.role.manage',
|
||||||
|
'prd.agent.user.view',
|
||||||
|
'prd.agent.user.manage',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.permission_slugs', function ($slugs): bool {
|
||||||
|
$list = is_array($slugs) ? $slugs : [];
|
||||||
|
|
||||||
|
return in_array('prd.agent.role.manage', $list, true)
|
||||||
|
&& in_array('prd.agent.user.manage', $list, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sync fails with clear error when agent manage menu actions are missing', function (): void {
|
||||||
|
DB::table('admin_menu_actions')
|
||||||
|
->whereIn('permission_code', ['agent.role.manage', 'agent.user.manage'])
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'agent_missing_catalog',
|
||||||
|
'name' => 'Missing Catalog',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(fn () => $role->syncLegacyPermissionSlugs(['prd.agent.role.manage']))
|
||||||
|
->toThrow(\Illuminate\Validation\ValidationException::class);
|
||||||
|
});
|
||||||
60
tests/Feature/AdminDrawRolePermissionRoundTripTest.php
Normal file
60
tests/Feature/AdminDrawRolePermissionRoundTripTest.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\AdminRole;
|
||||||
|
use App\Models\AdminUser;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function (): void {
|
||||||
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role with only draw view slug does not round-trip as manage', function (): void {
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'draw_view_only',
|
||||||
|
'name' => 'Draw view only',
|
||||||
|
]);
|
||||||
|
$role->syncLegacyPermissionSlugs(['prd.draw_result.view']);
|
||||||
|
|
||||||
|
expect($role->legacyPermissionSlugs())
|
||||||
|
->toContain('prd.draw_result.view')
|
||||||
|
->not->toContain('prd.draw_result.manage');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform role sync can drop draw manage while keeping view', function (): void {
|
||||||
|
$super = AdminUser::query()->create([
|
||||||
|
'username' => 'draw_perm_super',
|
||||||
|
'name' => 'Super',
|
||||||
|
'email' => null,
|
||||||
|
'password' => Hash::make('secret-strong'),
|
||||||
|
'status' => 0,
|
||||||
|
]);
|
||||||
|
grantSuperAdminRole($super);
|
||||||
|
$token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
||||||
|
|
||||||
|
$role = AdminRole::query()->create([
|
||||||
|
'slug' => 'draw_toggle_test',
|
||||||
|
'name' => 'Draw toggle test',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->putJson('/api/v1/admin/admin-roles/'.$role->id.'/permissions', [
|
||||||
|
'permission_slugs' => ['prd.draw_result.view', 'prd.draw_result.manage'],
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.permission_slugs', fn ($slugs): bool => in_array('prd.draw_result.manage', $slugs, true));
|
||||||
|
|
||||||
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
||||||
|
->putJson('/api/v1/admin/admin-roles/'.$role->id.'/permissions', [
|
||||||
|
'permission_slugs' => ['prd.draw_result.view'],
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.permission_slugs', function ($slugs): bool {
|
||||||
|
$list = is_array($slugs) ? $slugs : [];
|
||||||
|
|
||||||
|
return in_array('prd.draw_result.view', $list, true)
|
||||||
|
&& ! in_array('prd.draw_result.manage', $list, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,8 @@ pest()->extend(TestCase::class)
|
|||||||
// ->use(RefreshDatabase::class)
|
// ->use(RefreshDatabase::class)
|
||||||
->in('Feature');
|
->in('Feature');
|
||||||
|
|
||||||
|
pest()->extend(TestCase::class)->in('Unit');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Expectations
|
| Expectations
|
||||||
|
|||||||
52
tests/Unit/ApiValidationErrorsTest.php
Normal file
52
tests/Unit/ApiValidationErrorsTest.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Support\ApiValidationErrors;
|
||||||
|
|
||||||
|
test('normalizes english regex error for code field in zh', function (): void {
|
||||||
|
$errors = ApiValidationErrors::normalize(
|
||||||
|
['code' => ['The code field format is invalid.']],
|
||||||
|
'zh',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($errors['code'][0])->toContain('编码只能使用字母、数字');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizes business shorthand unique in zh', function (): void {
|
||||||
|
$errors = ApiValidationErrors::normalize(['code' => ['unique']], 'zh');
|
||||||
|
|
||||||
|
expect($errors['code'][0])->toBe('该内容已存在,请更换后重试。');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('summary joins multiple field errors', function (): void {
|
||||||
|
$normalized = [
|
||||||
|
'code' => ['编码格式不正确。'],
|
||||||
|
'name' => ['名称不能为空。'],
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(ApiValidationErrors::summary($normalized))->toBe('编码格式不正确。;名称不能为空。');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps already localized chinese messages', function (): void {
|
||||||
|
$message = '最小下注额不能小于 0';
|
||||||
|
$errors = ApiValidationErrors::normalize(['items.0.min_bet_amount' => [$message]], 'zh');
|
||||||
|
|
||||||
|
expect($errors['items.0.min_bet_amount'][0])->toBe($message);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizes english min length for password in zh', function (): void {
|
||||||
|
$errors = ApiValidationErrors::normalize(
|
||||||
|
['password' => ['The password field must be at least 8 characters.']],
|
||||||
|
'zh',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($errors['password'][0])->toBe('密码至少需要 8 个字符。');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizes exact draw items message in zh', function (): void {
|
||||||
|
$errors = ApiValidationErrors::normalize(
|
||||||
|
['items' => ['items must contain the complete 23 draw prize slots.']],
|
||||||
|
'zh',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($errors['items'][0])->toContain('23');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user