feat(API): 역할/권한 관리 API 개선
- RoleController: 역할 CRUD API 개선 - RolePermissionController: 역할별 권한 설정 API 추가 - RoleService/RolePermissionService: 비즈니스 로직 분리 - Role 모델: is_hidden 필드 추가 (시스템 역할 숨김) - 역할 숨김 마이그레이션 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,38 +9,73 @@
|
|||||||
|
|
||||||
class RoleController extends Controller
|
class RoleController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* 역할 목록 조회
|
||||||
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
return ApiResponse::handle(function () use ($request) {
|
return ApiResponse::handle(function () use ($request) {
|
||||||
return RoleService::index($request->all());
|
return RoleService::index($request->all());
|
||||||
}, '역할 목록 조회');
|
}, __('message.fetched'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할 생성
|
||||||
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
return ApiResponse::handle(function () use ($request) {
|
return ApiResponse::handle(function () use ($request) {
|
||||||
return RoleService::store($request->all());
|
return RoleService::store($request->all());
|
||||||
}, '역할 생성');
|
}, __('message.created'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할 상세 조회
|
||||||
|
*/
|
||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
return ApiResponse::handle(function () use ($id) {
|
return ApiResponse::handle(function () use ($id) {
|
||||||
return RoleService::show((int) $id);
|
return RoleService::show((int) $id);
|
||||||
}, '역할 상세 조회');
|
}, __('message.fetched'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할 수정
|
||||||
|
*/
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
return ApiResponse::handle(function () use ($request, $id) {
|
return ApiResponse::handle(function () use ($request, $id) {
|
||||||
return RoleService::update((int) $id, $request->all());
|
return RoleService::update((int) $id, $request->all());
|
||||||
}, '역할 수정');
|
}, __('message.updated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할 삭제
|
||||||
|
*/
|
||||||
public function destroy($id)
|
public function destroy($id)
|
||||||
{
|
{
|
||||||
return ApiResponse::handle(function () use ($id) {
|
return ApiResponse::handle(function () use ($id) {
|
||||||
return RoleService::destroy((int) $id);
|
return RoleService::destroy((int) $id);
|
||||||
}, '역할 삭제');
|
}, __('message.deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할 통계 조회
|
||||||
|
*/
|
||||||
|
public function stats()
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () {
|
||||||
|
return RoleService::stats();
|
||||||
|
}, __('message.fetched'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 역할 목록 (드롭다운용)
|
||||||
|
*/
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () {
|
||||||
|
return RoleService::active();
|
||||||
|
}, __('message.fetched'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,64 @@ public function sync($id, Request $request)
|
|||||||
return RolePermissionService::sync((int) $id, $request->all());
|
return RolePermissionService::sync((int) $id, $request->all());
|
||||||
}, '역할 퍼미션 동기화');
|
}, '역할 퍼미션 동기화');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 권한 매트릭스용 메뉴 트리 조회
|
||||||
|
*/
|
||||||
|
public function menus()
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () {
|
||||||
|
return RolePermissionService::menus();
|
||||||
|
}, __('message.fetched'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 역할의 권한 매트릭스 조회
|
||||||
|
*/
|
||||||
|
public function matrix($id)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($id) {
|
||||||
|
return RolePermissionService::matrix((int) $id);
|
||||||
|
}, __('message.fetched'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 메뉴의 특정 권한 토글
|
||||||
|
*/
|
||||||
|
public function toggle($id, Request $request)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($id, $request) {
|
||||||
|
return RolePermissionService::toggle((int) $id, $request->all());
|
||||||
|
}, __('message.updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 권한 허용
|
||||||
|
*/
|
||||||
|
public function allowAll($id)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($id) {
|
||||||
|
return RolePermissionService::allowAll((int) $id);
|
||||||
|
}, __('message.updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모든 권한 거부
|
||||||
|
*/
|
||||||
|
public function denyAll($id)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($id) {
|
||||||
|
return RolePermissionService::denyAll((int) $id);
|
||||||
|
}, __('message.updated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 기본 권한으로 초기화 (view만 허용)
|
||||||
|
*/
|
||||||
|
public function reset($id)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($id) {
|
||||||
|
return RolePermissionService::reset((int) $id);
|
||||||
|
}, __('message.updated'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,29 +5,83 @@
|
|||||||
use App\Models\Commons\IdeHelperRole;
|
use App\Models\Commons\IdeHelperRole;
|
||||||
use App\Models\Members\UserRole;
|
use App\Models\Members\UserRole;
|
||||||
use App\Models\Tenants\Tenant;
|
use App\Models\Tenants\Tenant;
|
||||||
|
use App\Traits\BelongsToTenant;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @mixin IdeHelperRole
|
* @mixin IdeHelperRole
|
||||||
*/
|
*/
|
||||||
class Role extends Model
|
class Role extends Model
|
||||||
{
|
{
|
||||||
|
use BelongsToTenant, SoftDeletes;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'name', 'description',
|
'tenant_id',
|
||||||
|
'name',
|
||||||
|
'guard_name',
|
||||||
|
'description',
|
||||||
|
'is_hidden',
|
||||||
|
'created_by',
|
||||||
|
'updated_by',
|
||||||
|
'deleted_by',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_hidden' => 'boolean',
|
||||||
|
'tenant_id' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관계: 메뉴 권한 (다대다)
|
||||||
|
*/
|
||||||
public function menuPermissions()
|
public function menuPermissions()
|
||||||
{
|
{
|
||||||
return $this->hasMany(RoleMenuPermission::class, 'role_id');
|
return $this->hasMany(RoleMenuPermission::class, 'role_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관계: 테넌트
|
||||||
|
*/
|
||||||
public function tenant()
|
public function tenant()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Tenant::class);
|
return $this->belongsTo(Tenant::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관계: 사용자 역할
|
||||||
|
*/
|
||||||
public function userRoles()
|
public function userRoles()
|
||||||
{
|
{
|
||||||
return $this->hasMany(UserRole::class);
|
return $this->hasMany(UserRole::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관계: 권한 (role_has_permissions 테이블 통해)
|
||||||
|
*/
|
||||||
|
public function permissions()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(
|
||||||
|
Permission::class,
|
||||||
|
'role_has_permissions',
|
||||||
|
'role_id',
|
||||||
|
'permission_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스코프: 공개된 역할만
|
||||||
|
*/
|
||||||
|
public function scopeVisible($query)
|
||||||
|
{
|
||||||
|
return $query->where('is_hidden', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스코프: 숨겨진 역할만
|
||||||
|
*/
|
||||||
|
public function scopeHidden($query)
|
||||||
|
{
|
||||||
|
return $query->where('is_hidden', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,4 +212,330 @@ public static function sync(int $roleId, array $params = [])
|
|||||||
|
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 권한 유형 목록 */
|
||||||
|
protected static function getPermissionTypes(): array
|
||||||
|
{
|
||||||
|
return config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve', 'export', 'manage']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 역할의 권한 매트릭스 조회 */
|
||||||
|
public static function matrix(int $roleId)
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||||
|
if (! $role) {
|
||||||
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
|
}
|
||||||
|
|
||||||
|
self::setTeam($tenantId);
|
||||||
|
|
||||||
|
// 역할에 부여된 권한 조회
|
||||||
|
$rolePermissions = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id')
|
||||||
|
->where('role_has_permissions.role_id', $roleId)
|
||||||
|
->where('permissions.guard_name', self::$guard)
|
||||||
|
->where('permissions.name', 'like', 'menu:%')
|
||||||
|
->pluck('permissions.name')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$permissions = [];
|
||||||
|
foreach ($rolePermissions as $permName) {
|
||||||
|
if (preg_match('/^menu:(\d+)\.(\w+)$/', $permName, $matches)) {
|
||||||
|
$menuId = (int) $matches[1];
|
||||||
|
$type = $matches[2];
|
||||||
|
|
||||||
|
if (! isset($permissions[$menuId])) {
|
||||||
|
$permissions[$menuId] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissions[$menuId][$type] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'role' => [
|
||||||
|
'id' => $role->id,
|
||||||
|
'name' => $role->name,
|
||||||
|
'description' => $role->description,
|
||||||
|
],
|
||||||
|
'permission_types' => self::getPermissionTypes(),
|
||||||
|
'permissions' => $permissions,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 메뉴 트리 조회 (권한 매트릭스 표시용) */
|
||||||
|
public static function menus()
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$menus = \App\Models\Menus\Menu::where('tenant_id', $tenantId)
|
||||||
|
->where('is_active', true)
|
||||||
|
->orderBy('sort_order', 'asc')
|
||||||
|
->orderBy('id', 'asc')
|
||||||
|
->get(['id', 'parent_id', 'name', 'code', 'path', 'icon', 'sort_order', 'is_active']);
|
||||||
|
|
||||||
|
// 트리 구조를 플랫한 배열로 변환 (depth 정보 포함)
|
||||||
|
$flatMenus = self::flattenMenuTree($menus->toArray(), null, 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'menus' => $flatMenus,
|
||||||
|
'permission_types' => self::getPermissionTypes(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 트리 구조를 플랫한 배열로 변환 (depth 정보 포함) */
|
||||||
|
protected static function flattenMenuTree(array $menus, ?int $parentId = null, int $depth = 0): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
$filteredMenus = array_filter($menus, fn ($m) => $m['parent_id'] === $parentId);
|
||||||
|
usort($filteredMenus, fn ($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));
|
||||||
|
|
||||||
|
foreach ($filteredMenus as $menu) {
|
||||||
|
$menu['depth'] = $depth;
|
||||||
|
$menu['has_children'] = count(array_filter($menus, fn ($m) => $m['parent_id'] === $menu['id'])) > 0;
|
||||||
|
$result[] = $menu;
|
||||||
|
|
||||||
|
// 자식 메뉴 재귀적으로 추가
|
||||||
|
$children = self::flattenMenuTree($menus, $menu['id'], $depth + 1);
|
||||||
|
$result = array_merge($result, $children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 특정 메뉴의 특정 권한 토글 */
|
||||||
|
public static function toggle(int $roleId, array $params = [])
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||||
|
if (! $role) {
|
||||||
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = Validator::make($params, [
|
||||||
|
'menu_id' => 'required|integer|min:1',
|
||||||
|
'permission_type' => ['required', 'string', Rule::in(self::getPermissionTypes())],
|
||||||
|
]);
|
||||||
|
if ($v->fails()) {
|
||||||
|
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||||
|
}
|
||||||
|
|
||||||
|
$menuId = (int) $params['menu_id'];
|
||||||
|
$permissionType = $params['permission_type'];
|
||||||
|
|
||||||
|
self::setTeam($tenantId);
|
||||||
|
|
||||||
|
$permissionName = "menu:{$menuId}.{$permissionType}";
|
||||||
|
|
||||||
|
// 권한 생성 또는 조회
|
||||||
|
$permission = Permission::firstOrCreate([
|
||||||
|
'name' => $permissionName,
|
||||||
|
'guard_name' => self::$guard,
|
||||||
|
'tenant_id' => $tenantId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 현재 권한 상태 확인
|
||||||
|
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
// 권한 제거
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->delete();
|
||||||
|
$newValue = false;
|
||||||
|
} else {
|
||||||
|
// 권한 부여
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'permission_id' => $permission->id,
|
||||||
|
]);
|
||||||
|
$newValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 하위 메뉴에 권한 전파
|
||||||
|
self::propagateToChildren($roleId, $menuId, $permissionType, $newValue, $tenantId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'menu_id' => $menuId,
|
||||||
|
'permission_type' => $permissionType,
|
||||||
|
'granted' => $newValue,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 하위 메뉴에 권한 전파 */
|
||||||
|
protected static function propagateToChildren(int $roleId, int $parentMenuId, string $permissionType, bool $value, int $tenantId): void
|
||||||
|
{
|
||||||
|
$children = \App\Models\Menus\Menu::where('parent_id', $parentMenuId)
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($children as $child) {
|
||||||
|
$permissionName = "menu:{$child->id}.{$permissionType}";
|
||||||
|
$permission = Permission::firstOrCreate([
|
||||||
|
'name' => $permissionName,
|
||||||
|
'guard_name' => self::$guard,
|
||||||
|
'tenant_id' => $tenantId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($value) {
|
||||||
|
// 권한 부여
|
||||||
|
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if (! $exists) {
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'permission_id' => $permission->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 권한 제거
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재귀적으로 하위 메뉴 처리
|
||||||
|
self::propagateToChildren($roleId, $child->id, $permissionType, $value, $tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 모든 권한 허용 */
|
||||||
|
public static function allowAll(int $roleId)
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||||
|
if (! $role) {
|
||||||
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
|
}
|
||||||
|
|
||||||
|
self::setTeam($tenantId);
|
||||||
|
|
||||||
|
$menus = \App\Models\Menus\Menu::where('tenant_id', $tenantId)
|
||||||
|
->where('is_active', true)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$permissionTypes = self::getPermissionTypes();
|
||||||
|
|
||||||
|
foreach ($menus as $menu) {
|
||||||
|
foreach ($permissionTypes as $type) {
|
||||||
|
$permissionName = "menu:{$menu->id}.{$type}";
|
||||||
|
$permission = Permission::firstOrCreate([
|
||||||
|
'name' => $permissionName,
|
||||||
|
'guard_name' => self::$guard,
|
||||||
|
'tenant_id' => $tenantId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 권한 부여
|
||||||
|
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if (! $exists) {
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'permission_id' => $permission->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 모든 권한 거부 */
|
||||||
|
public static function denyAll(int $roleId)
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||||
|
if (! $role) {
|
||||||
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
|
}
|
||||||
|
|
||||||
|
self::setTeam($tenantId);
|
||||||
|
|
||||||
|
$menus = \App\Models\Menus\Menu::where('tenant_id', $tenantId)
|
||||||
|
->where('is_active', true)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$permissionTypes = self::getPermissionTypes();
|
||||||
|
|
||||||
|
foreach ($menus as $menu) {
|
||||||
|
foreach ($permissionTypes as $type) {
|
||||||
|
$permissionName = "menu:{$menu->id}.{$type}";
|
||||||
|
$permission = Permission::where('name', $permissionName)
|
||||||
|
->where('guard_name', self::$guard)
|
||||||
|
->where('tenant_id', $tenantId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($permission) {
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 기본 권한으로 초기화 (view만 허용) */
|
||||||
|
public static function reset(int $roleId)
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$role = self::loadRoleOrError($roleId, $tenantId);
|
||||||
|
if (! $role) {
|
||||||
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
|
}
|
||||||
|
|
||||||
|
self::setTeam($tenantId);
|
||||||
|
|
||||||
|
// 1. 먼저 모든 권한 제거
|
||||||
|
self::denyAll($roleId);
|
||||||
|
|
||||||
|
// 2. view 권한만 허용
|
||||||
|
$menus = \App\Models\Menus\Menu::where('tenant_id', $tenantId)
|
||||||
|
->where('is_active', true)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($menus as $menu) {
|
||||||
|
$permissionName = "menu:{$menu->id}.view";
|
||||||
|
$permission = Permission::firstOrCreate([
|
||||||
|
'name' => $permissionName,
|
||||||
|
'guard_name' => self::$guard,
|
||||||
|
'tenant_id' => $tenantId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 권한 부여
|
||||||
|
$exists = \Illuminate\Support\Facades\DB::table('role_has_permissions')
|
||||||
|
->where('role_id', $roleId)
|
||||||
|
->where('permission_id', $permission->id)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if (! $exists) {
|
||||||
|
\Illuminate\Support\Facades\DB::table('role_has_permissions')->insert([
|
||||||
|
'role_id' => $roleId,
|
||||||
|
'permission_id' => $permission->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ public static function index(array $params = [])
|
|||||||
|
|
||||||
$query = Role::query()
|
$query = Role::query()
|
||||||
->where('tenant_id', $tenantId)
|
->where('tenant_id', $tenantId)
|
||||||
->where('guard_name', self::$guard);
|
->where('guard_name', self::$guard)
|
||||||
|
->withCount(['permissions', 'users']);
|
||||||
|
|
||||||
|
// 검색 필터
|
||||||
if ($q !== '') {
|
if ($q !== '') {
|
||||||
$query->where(function ($w) use ($q) {
|
$query->where(function ($w) use ($q) {
|
||||||
$w->where('name', 'like', "%{$q}%")
|
$w->where('name', 'like', "%{$q}%")
|
||||||
@@ -31,6 +33,12 @@ public static function index(array $params = [])
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 숨김 상태 필터
|
||||||
|
if (isset($params['is_hidden'])) {
|
||||||
|
$isHidden = filter_var($params['is_hidden'], FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$query->where('is_hidden', $isHidden);
|
||||||
|
}
|
||||||
|
|
||||||
$list = $query->orderByDesc('id')
|
$list = $query->orderByDesc('id')
|
||||||
->paginate($size, ['*'], 'page', $page);
|
->paginate($size, ['*'], 'page', $page);
|
||||||
|
|
||||||
@@ -41,6 +49,7 @@ public static function index(array $params = [])
|
|||||||
public static function store(array $params = [])
|
public static function store(array $params = [])
|
||||||
{
|
{
|
||||||
$tenantId = (int) app('tenant_id');
|
$tenantId = (int) app('tenant_id');
|
||||||
|
$userId = app('api_user');
|
||||||
|
|
||||||
$v = Validator::make($params, [
|
$v = Validator::make($params, [
|
||||||
'name' => [
|
'name' => [
|
||||||
@@ -50,6 +59,7 @@ public static function store(array $params = [])
|
|||||||
->where('guard_name', self::$guard)),
|
->where('guard_name', self::$guard)),
|
||||||
],
|
],
|
||||||
'description' => 'nullable|string|max:255',
|
'description' => 'nullable|string|max:255',
|
||||||
|
'is_hidden' => 'sometimes|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($v->fails()) {
|
if ($v->fails()) {
|
||||||
@@ -64,9 +74,11 @@ public static function store(array $params = [])
|
|||||||
'guard_name' => self::$guard,
|
'guard_name' => self::$guard,
|
||||||
'name' => $v->validated()['name'],
|
'name' => $v->validated()['name'],
|
||||||
'description' => $params['description'] ?? null,
|
'description' => $params['description'] ?? null,
|
||||||
|
'is_hidden' => $params['is_hidden'] ?? false,
|
||||||
|
'created_by' => $userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $role;
|
return $role->loadCount(['permissions', 'users']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 단건 */
|
/** 단건 */
|
||||||
@@ -76,6 +88,7 @@ public static function show(int $id)
|
|||||||
|
|
||||||
$role = Role::where('tenant_id', $tenantId)
|
$role = Role::where('tenant_id', $tenantId)
|
||||||
->where('guard_name', self::$guard)
|
->where('guard_name', self::$guard)
|
||||||
|
->withCount(['permissions', 'users'])
|
||||||
->find($id);
|
->find($id);
|
||||||
|
|
||||||
if (! $role) {
|
if (! $role) {
|
||||||
@@ -89,6 +102,7 @@ public static function show(int $id)
|
|||||||
public static function update(int $id, array $params = [])
|
public static function update(int $id, array $params = [])
|
||||||
{
|
{
|
||||||
$tenantId = (int) app('tenant_id');
|
$tenantId = (int) app('tenant_id');
|
||||||
|
$userId = app('api_user');
|
||||||
|
|
||||||
$role = Role::where('tenant_id', $tenantId)
|
$role = Role::where('tenant_id', $tenantId)
|
||||||
->where('guard_name', self::$guard)
|
->where('guard_name', self::$guard)
|
||||||
@@ -106,21 +120,26 @@ public static function update(int $id, array $params = [])
|
|||||||
->ignore($role->id),
|
->ignore($role->id),
|
||||||
],
|
],
|
||||||
'description' => 'sometimes|nullable|string|max:255',
|
'description' => 'sometimes|nullable|string|max:255',
|
||||||
|
'is_hidden' => 'sometimes|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($v->fails()) {
|
if ($v->fails()) {
|
||||||
return ['error' => $v->errors()->first(), 'code' => 422];
|
return ['error' => $v->errors()->first(), 'code' => 422];
|
||||||
}
|
}
|
||||||
|
|
||||||
$role->fill($v->validated())->save();
|
$updateData = $v->validated();
|
||||||
|
$updateData['updated_by'] = $userId;
|
||||||
|
|
||||||
return $role->fresh();
|
$role->fill($updateData)->save();
|
||||||
|
|
||||||
|
return $role->fresh()->loadCount(['permissions', 'users']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 삭제 (하드삭제) */
|
/** 삭제 (Soft Delete) */
|
||||||
public static function destroy(int $id)
|
public static function destroy(int $id)
|
||||||
{
|
{
|
||||||
$tenantId = (int) app('tenant_id');
|
$tenantId = (int) app('tenant_id');
|
||||||
|
$userId = app('api_user');
|
||||||
|
|
||||||
$role = Role::where('tenant_id', $tenantId)
|
$role = Role::where('tenant_id', $tenantId)
|
||||||
->where('guard_name', self::$guard)
|
->where('guard_name', self::$guard)
|
||||||
@@ -130,10 +149,43 @@ public static function destroy(int $id)
|
|||||||
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404];
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::transaction(function () use ($role) {
|
DB::transaction(function () use ($role, $userId) {
|
||||||
$role->delete(); // Spatie Role 기본: soft delete 없음
|
// 권한 연결 해제
|
||||||
|
$role->permissions()->detach();
|
||||||
|
|
||||||
|
// Soft Delete
|
||||||
|
$role->update(['deleted_by' => $userId]);
|
||||||
|
$role->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
return 'success';
|
return 'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 통계 조회 */
|
||||||
|
public static function stats()
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
$baseQuery = Role::where('tenant_id', $tenantId)
|
||||||
|
->where('guard_name', self::$guard);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => (clone $baseQuery)->count(),
|
||||||
|
'visible' => (clone $baseQuery)->where('is_hidden', false)->count(),
|
||||||
|
'hidden' => (clone $baseQuery)->where('is_hidden', true)->count(),
|
||||||
|
'with_users' => (clone $baseQuery)->has('users')->count(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 활성 역할 목록 (드롭다운용) */
|
||||||
|
public static function active()
|
||||||
|
{
|
||||||
|
$tenantId = (int) app('tenant_id');
|
||||||
|
|
||||||
|
return Role::where('tenant_id', $tenantId)
|
||||||
|
->where('guard_name', self::$guard)
|
||||||
|
->where('is_hidden', false)
|
||||||
|
->orderBy('name')
|
||||||
|
->get(['id', 'name', 'description']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('roles', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_hidden')
|
||||||
|
->default(false)
|
||||||
|
->after('description')
|
||||||
|
->comment('숨김 여부 (공개/숨김)');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('roles', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user