diff --git a/app/Http/Middleware/EnsureHQMember.php b/app/Http/Middleware/EnsureHQMember.php index 7d40e1ec..ee6a6b19 100644 --- a/app/Http/Middleware/EnsureHQMember.php +++ b/app/Http/Middleware/EnsureHQMember.php @@ -32,8 +32,18 @@ public function handle(Request $request, Closure $next): Response $request->session()->invalidate(); $request->session()->regenerateToken(); - return redirect('/login') - ->withErrors(['email' => '본사 소속 직원만 관리자 패널에 접근할 수 있습니다.']); + $message = '본사 소속 직원만 관리자 패널에 접근할 수 있습니다.'; + + // HTMX/AJAX 요청인 경우 JSON 응답 + if ($request->header('HX-Request') || $request->expectsJson() || $request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => $message, + 'redirect' => '/login', + ], 403); + } + + return redirect('/login')->withErrors(['email' => $message]); } // 비활성 계정인 경우 강제 로그아웃 @@ -42,8 +52,18 @@ public function handle(Request $request, Closure $next): Response $request->session()->invalidate(); $request->session()->regenerateToken(); - return redirect('/login') - ->withErrors(['email' => '비활성화된 계정입니다. 관리자에게 문의하세요.']); + $message = '비활성화된 계정입니다. 관리자에게 문의하세요.'; + + // HTMX/AJAX 요청인 경우 JSON 응답 + if ($request->header('HX-Request') || $request->expectsJson() || $request->ajax()) { + return response()->json([ + 'success' => false, + 'message' => $message, + 'redirect' => '/login', + ], 403); + } + + return redirect('/login')->withErrors(['email' => $message]); } return $next($request); diff --git a/app/Http/Middleware/EnsureSuperAdmin.php b/app/Http/Middleware/EnsureSuperAdmin.php index 8daff1f9..1843180a 100644 --- a/app/Http/Middleware/EnsureSuperAdmin.php +++ b/app/Http/Middleware/EnsureSuperAdmin.php @@ -22,17 +22,18 @@ public function handle(Request $request, Closure $next): Response $user = $request->user(); if (! $user || ! $user->isSuperAdmin()) { - // AJAX/API 요청인 경우 JSON 응답 - if ($request->expectsJson() || $request->ajax()) { + $message = '슈퍼관리자만 접근할 수 있는 기능입니다.'; + + // HTMX/AJAX/API 요청인 경우 JSON 응답 + if ($request->header('HX-Request') || $request->expectsJson() || $request->ajax()) { return response()->json([ 'success' => false, - 'message' => '슈퍼관리자만 접근할 수 있는 기능입니다.', + 'message' => $message, ], 403); } // 웹 요청인 경우 리다이렉트 - return redirect()->back() - ->with('error', '슈퍼관리자만 접근할 수 있는 기능입니다.'); + return redirect()->back()->with('error', $message); } return $next($request); diff --git a/app/Services/RoleService.php b/app/Services/RoleService.php index 9e5dff85..33ff2ca3 100644 --- a/app/Services/RoleService.php +++ b/app/Services/RoleService.php @@ -7,7 +7,6 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; -use Spatie\Permission\Models\Role as SpatieRole; class RoleService { @@ -55,11 +54,11 @@ public function getRoles(array $filters = [], int $perPage = 15): LengthAwarePag /** * 특정 역할 조회 */ - public function getRoleById(int $id): ?SpatieRole + public function getRoleById(int $id): ?Role { $tenantId = session('selected_tenant_id'); - $query = SpatieRole::query()->with('permissions'); + $query = Role::query()->with('permissions'); // Tenant 필터링 (선택된 경우에만) if ($tenantId && $tenantId !== 'all') { @@ -72,13 +71,13 @@ public function getRoleById(int $id): ?SpatieRole /** * 역할 생성 */ - public function createRole(array $data): SpatieRole + public function createRole(array $data): Role { $tenantId = session('selected_tenant_id'); $effectiveTenantId = ($tenantId && $tenantId !== 'all') ? $tenantId : null; $guardName = $data['guard_name'] ?? 'api'; - $role = SpatieRole::create([ + $role = Role::create([ 'tenant_id' => $effectiveTenantId, 'guard_name' => $guardName, 'name' => $data['name'], @@ -125,7 +124,7 @@ public function updateRole(int $id, array $data): bool /** * 메뉴 권한 동기화 */ - protected function syncMenuPermissions(SpatieRole $role, array $menuPermissions, ?int $tenantId, string $guardName): void + protected function syncMenuPermissions(Role $role, array $menuPermissions, ?int $tenantId, string $guardName): void { // 기존 메뉴 권한 모두 제거 DB::table('role_has_permissions') @@ -195,7 +194,7 @@ public function isNameExists(string $name, ?int $excludeId = null): bool { $tenantId = session('selected_tenant_id'); - $query = SpatieRole::where('guard_name', 'web') + $query = Role::where('guard_name', 'web') ->where('name', $name); if ($tenantId && $tenantId !== 'all') { @@ -216,7 +215,7 @@ public function getActiveRoles(): Collection { $tenantId = session('selected_tenant_id'); - $query = SpatieRole::query()->where('guard_name', 'web'); + $query = Role::query()->where('guard_name', 'web'); // Tenant 필터링 (선택된 경우에만) if ($tenantId && $tenantId !== 'all') { @@ -233,7 +232,7 @@ public function getRoleStats(): array { $tenantId = session('selected_tenant_id'); - $baseQuery = SpatieRole::query()->where('guard_name', 'web'); + $baseQuery = Role::query()->where('guard_name', 'web'); // Tenant 필터링 (선택된 경우에만) if ($tenantId && $tenantId !== 'all') { diff --git a/bootstrap/app.php b/bootstrap/app.php index c81249de..5cc15bb9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,6 +3,7 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Illuminate\Http\Request; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( @@ -26,5 +27,18 @@ ]); }) ->withExceptions(function (Exceptions $exceptions): void { - // + // HTMX/AJAX 요청 시 JSON 에러 응답 반환 + $exceptions->render(function (Throwable $e, Request $request) { + if ($request->header('HX-Request') || $request->expectsJson() || $request->ajax()) { + $statusCode = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500; + + return response()->json([ + 'success' => false, + 'message' => $e->getMessage() ?: '서버 오류가 발생했습니다.', + 'exception' => config('app.debug') ? get_class($e) : null, + ], $statusCode); + } + + return null; // 기본 핸들러로 위임 + }); })->create();