Files
sam-manage/app/Services/RoleService.php
kent 85d50e9d8b fix(mng): HTMX 요청 시 JSON 에러 응답 반환 및 Role 테넌트 분리
- 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>
2026-01-07 19:52:45 +09:00

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(),
];
}
}