- EnsureHQMember: HTMX/AJAX 요청 시 JSON 응답 반환 - EnsureSuperAdmin: HX-Request 헤더 체크 추가 - bootstrap/app.php: 전역 Exception Handler에서 HTMX 요청 처리 - RoleService: SpatieRole → App\Models\Role로 변경하여 테넌트별 역할 분리 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
248 lines
7.0 KiB
PHP
248 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Permission;
|
|
use App\Models\Role;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class RoleService
|
|
{
|
|
/**
|
|
* 역할 목록 조회 (페이지네이션)
|
|
*/
|
|
public function getRoles(array $filters = [], int $perPage = 15): LengthAwarePaginator
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Role::query()->withCount('permissions');
|
|
|
|
// Guard 필터링 (선택된 경우에만)
|
|
if (! empty($filters['guard_name'])) {
|
|
$query->where('guard_name', $filters['guard_name']);
|
|
}
|
|
|
|
// 전체 보기일 때 tenant 정보 로드
|
|
if (! $tenantId || $tenantId === 'all') {
|
|
$query->with('tenant');
|
|
}
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId && $tenantId !== 'all') {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
// 검색 필터
|
|
if (! empty($filters['search'])) {
|
|
$search = $filters['search'];
|
|
$query->where(function ($q) use ($search) {
|
|
$q->where('name', 'like', "%{$search}%")
|
|
->orWhere('description', 'like', "%{$search}%");
|
|
});
|
|
}
|
|
|
|
// 정렬
|
|
$sortBy = $filters['sort_by'] ?? 'id';
|
|
$sortDirection = $filters['sort_direction'] ?? 'desc';
|
|
$query->orderBy($sortBy, $sortDirection);
|
|
|
|
return $query->paginate($perPage);
|
|
}
|
|
|
|
/**
|
|
* 특정 역할 조회
|
|
*/
|
|
public function getRoleById(int $id): ?Role
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Role::query()->with('permissions');
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId && $tenantId !== 'all') {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return $query->find($id);
|
|
}
|
|
|
|
/**
|
|
* 역할 생성
|
|
*/
|
|
public function createRole(array $data): Role
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
$effectiveTenantId = ($tenantId && $tenantId !== 'all') ? $tenantId : null;
|
|
$guardName = $data['guard_name'] ?? 'api';
|
|
|
|
$role = Role::create([
|
|
'tenant_id' => $effectiveTenantId,
|
|
'guard_name' => $guardName,
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
]);
|
|
|
|
// 메뉴 권한 동기화 (있는 경우)
|
|
if (! empty($data['menu_permissions'])) {
|
|
$this->syncMenuPermissions($role, $data['menu_permissions'], $effectiveTenantId, $guardName);
|
|
}
|
|
|
|
return $role->fresh(['permissions']);
|
|
}
|
|
|
|
/**
|
|
* 역할 수정
|
|
*/
|
|
public function updateRole(int $id, array $data): bool
|
|
{
|
|
$role = $this->getRoleById($id);
|
|
|
|
if (! $role) {
|
|
return false;
|
|
}
|
|
|
|
$tenantId = session('selected_tenant_id');
|
|
$effectiveTenantId = ($tenantId && $tenantId !== 'all') ? $tenantId : null;
|
|
$guardName = $data['guard_name'] ?? $role->guard_name;
|
|
|
|
$updated = $role->update([
|
|
'name' => $data['name'] ?? $role->name,
|
|
'guard_name' => $guardName,
|
|
'description' => $data['description'] ?? $role->description,
|
|
]);
|
|
|
|
// 메뉴 권한 동기화 (있는 경우)
|
|
if (isset($data['menu_permissions'])) {
|
|
$this->syncMenuPermissions($role, $data['menu_permissions'], $effectiveTenantId, $guardName);
|
|
}
|
|
|
|
return $updated;
|
|
}
|
|
|
|
/**
|
|
* 메뉴 권한 동기화
|
|
*/
|
|
protected function syncMenuPermissions(Role $role, array $menuPermissions, ?int $tenantId, string $guardName): void
|
|
{
|
|
// 기존 메뉴 권한 모두 제거
|
|
DB::table('role_has_permissions')
|
|
->where('role_id', $role->id)
|
|
->whereIn('permission_id', function ($query) use ($guardName) {
|
|
$query->select('id')
|
|
->from('permissions')
|
|
->where('guard_name', $guardName)
|
|
->where('name', 'like', 'menu:%');
|
|
})
|
|
->delete();
|
|
|
|
// 새로운 권한 부여
|
|
foreach ($menuPermissions as $menuId => $types) {
|
|
if (! is_array($types)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($types as $type) {
|
|
$permissionName = "menu:{$menuId}.{$type}";
|
|
|
|
// 권한 생성 또는 조회
|
|
$permission = Permission::firstOrCreate(
|
|
['name' => $permissionName, 'guard_name' => $guardName, 'tenant_id' => $tenantId],
|
|
['created_by' => auth()->id()]
|
|
);
|
|
|
|
// 권한 부여
|
|
$exists = DB::table('role_has_permissions')
|
|
->where('role_id', $role->id)
|
|
->where('permission_id', $permission->id)
|
|
->exists();
|
|
|
|
if (! $exists) {
|
|
DB::table('role_has_permissions')->insert([
|
|
'role_id' => $role->id,
|
|
'permission_id' => $permission->id,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 역할 삭제
|
|
*/
|
|
public function deleteRole(int $id): bool
|
|
{
|
|
$role = $this->getRoleById($id);
|
|
|
|
if (! $role) {
|
|
return false;
|
|
}
|
|
|
|
// 권한 연결 해제
|
|
$role->permissions()->detach();
|
|
|
|
// 사용자 연결은 FK CASCADE가 자동 처리 (model_has_roles.role_id → ON DELETE CASCADE)
|
|
|
|
return $role->delete();
|
|
}
|
|
|
|
/**
|
|
* 역할 이름 중복 체크
|
|
*/
|
|
public function isNameExists(string $name, ?int $excludeId = null): bool
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Role::where('guard_name', 'web')
|
|
->where('name', $name);
|
|
|
|
if ($tenantId && $tenantId !== 'all') {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
if ($excludeId) {
|
|
$query->where('id', '!=', $excludeId);
|
|
}
|
|
|
|
return $query->exists();
|
|
}
|
|
|
|
/**
|
|
* 활성 역할 목록 (드롭다운용)
|
|
*/
|
|
public function getActiveRoles(): Collection
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$query = Role::query()->where('guard_name', 'web');
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId && $tenantId !== 'all') {
|
|
$query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return $query->orderBy('name')->get(['id', 'name', 'description']);
|
|
}
|
|
|
|
/**
|
|
* 역할 통계
|
|
*/
|
|
public function getRoleStats(): array
|
|
{
|
|
$tenantId = session('selected_tenant_id');
|
|
|
|
$baseQuery = Role::query()->where('guard_name', 'web');
|
|
|
|
// Tenant 필터링 (선택된 경우에만)
|
|
if ($tenantId && $tenantId !== 'all') {
|
|
$baseQuery->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
return [
|
|
'total' => (clone $baseQuery)->count(),
|
|
'with_permissions' => (clone $baseQuery)->has('permissions')->count(),
|
|
];
|
|
}
|
|
}
|