setPermissionsTeamId($tenantId); } /** 역할 로드 (테넌트/가드 검증) */ protected static function loadRoleOrError(int $roleId, int $tenantId): ?Role { $role = Role::query() ->where('tenant_id', $tenantId) ->where('guard_name', self::$guard) ->find($roleId); return $role; } /** A) permission_names[] → 그대로 사용 * B) menus[] + actions[] → "menu:{id}.{act}" 배열로 변환(필요 시 Permission 생성) */ protected static function resolvePermissionNames(int $tenantId, array $params): array { $names = []; if (!empty($params['permission_names']) && is_array($params['permission_names'])) { // 문자열 배열만 추림 foreach ($params['permission_names'] as $n) { if (is_string($n) && $n !== '') $names[] = trim($n); } } if (!empty($params['menus']) && is_array($params['menus']) && !empty($params['actions']) && is_array($params['actions'])) { $allowed = config('authz.menu_actions', ['view','create','update','delete','approve']); $acts = array_values(array_unique(array_filter(array_map('trim', $params['actions'])))); $acts = array_intersect($acts, $allowed); $menuIds = array_values(array_unique(array_map('intval', $params['menus']))); foreach ($menuIds as $mid) { foreach ($acts as $act) { $names[] = "menu:{$mid}.{$act}"; } } } // 빈/중복 제거 $names = array_values(array_unique(array_filter($names))); // 존재하지 않는 Permission은 생성(tenant+guard 포함) foreach ($names as $permName) { Permission::firstOrCreate([ 'tenant_id' => $tenantId, 'guard_name' => self::$guard, 'name' => $permName, ]); } return $names; } /** 역할의 퍼미션 목록 */ public static function list(int $roleId) { $tenantId = (int) app('tenant_id'); $role = self::loadRoleOrError($roleId, $tenantId); if (!$role) { return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404]; } self::setTeam($tenantId); $perms = $role->permissions() ->where('tenant_id', $tenantId) ->where('guard_name', self::$guard) ->orderBy('name') ->get(['id','tenant_id','name','guard_name','created_at','updated_at']); return $perms; } /** 부여 (중복 무시) */ public static function grant(int $roleId, array $params = []) { $tenantId = (int) app('tenant_id'); $role = self::loadRoleOrError($roleId, $tenantId); if (!$role) { return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404]; } // 유효성: 두 방식 중 하나만 요구하진 않지만, 최소 하나는 있어야 함 $v = Validator::make($params, [ 'permission_names' => 'sometimes|array', 'permission_names.*' => 'string|min:1', 'menus' => 'sometimes|array', 'menus.*' => 'integer|min:1', 'actions' => 'sometimes|array', 'actions.*' => [ 'string', Rule::in(config('authz.menu_actions', ['view','create','update','delete','approve'])), ], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) { return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422]; } self::setTeam($tenantId); $names = self::resolvePermissionNames($tenantId, $params); if (empty($names)) { return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422]; } // Spatie: 이름 배열 부여 OK (teams 컨텍스트 적용됨) $role->givePermissionTo($names); return 'success'; } /** 회수 (없는 건 무시) */ public static function revoke(int $roleId, array $params = []) { $tenantId = (int) app('tenant_id'); $role = self::loadRoleOrError($roleId, $tenantId); if (!$role) { return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404]; } $v = Validator::make($params, [ 'permission_names' => 'sometimes|array', 'permission_names.*' => 'string|min:1', 'menus' => 'sometimes|array', 'menus.*' => 'integer|min:1', 'actions' => 'sometimes|array', 'actions.*' => [ 'string', Rule::in(config('authz.menu_actions', ['view','create','update','delete','approve'])), ], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) { return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422]; } self::setTeam($tenantId); $names = self::resolvePermissionNames($tenantId, $params); if (empty($names)) { return ['error' => '유효한 퍼미션이 없습니다.', 'code' => 422]; } $role->revokePermissionTo($names); return 'success'; } /** 동기화(완전 교체) */ public static function sync(int $roleId, array $params = []) { $tenantId = (int) app('tenant_id'); $role = self::loadRoleOrError($roleId, $tenantId); if (!$role) { return ['error' => '역할을 찾을 수 없습니다.', 'code' => 404]; } $v = Validator::make($params, [ 'permission_names' => 'sometimes|array', 'permission_names.*' => 'string|min:1', 'menus' => 'sometimes|array', 'menus.*' => 'integer|min:1', 'actions' => 'sometimes|array', 'actions.*' => [ 'string', Rule::in(config('authz.menu_actions', ['view','create','update','delete','approve'])), ], ]); if ($v->fails()) { return ['error' => $v->errors()->first(), 'code' => 422]; } if (empty($params['permission_names']) && (empty($params['menus']) || empty($params['actions']))) { return ['error' => 'permission_names 또는 menus+actions 중 하나는 필요합니다.', 'code' => 422]; } self::setTeam($tenantId); $names = self::resolvePermissionNames($tenantId, $params); // 존재하지 않으면 생성 // 동기화 $role->syncPermissions($names); return 'success'; } }