page ?? 1; $pageSize = $request->size ?? 10; $query = User::whereHas('userTenants', function ($q) { $q->active(); })->debug(); $query = $query->paginate($pageSize, ['*'], 'page', $pageNo); return $query; } /** * 단일 회원 조회 */ public static function getMember(int $userNo) { $query = User::whereHas('userTenants', function ($q) { $q->active(); })->where('id', $userNo); return $query->first(); } /** * 내정보 확인 */ public static function getMyInfo() { $apiUser = app('api_user'); $user = User::find($apiUser); $data['user'] = $user; $tenantId = app('tenant_id'); if ($tenantId) { $tenant = Tenant::find($tenantId); $data['tenant'] = $tenant; } return $data; } /** * 내정보 수정 */ public static function getMyUpdate($request) { $apiUser = app('api_user'); // 요청으로 받은 수정 데이터 유효성 검사 $validatedData = $request->validate([ 'name' => 'sometimes|string|max:255', 'phone' => 'sometimes|string|max:20', 'email' => 'sometimes|email|max:100', 'options' => 'nullable|json', 'profile_photo_path' => 'nullable|string|max:255', ]); $user = User::find($apiUser); if (! $user) { return ['error' => 'User not found.', 'code' => 404]; } // 사용자 정보 업데이트 $user->update($validatedData); // 수정 성공 시 success 반환 return 'success'; } /** * 내 비밀번호 수정 */ public static function setMyPassword($request) { $apiUserId = app('api_user'); // 현재 로그인한 사용자 PK // 유효성 검사 (확인 비밀번호는 선택) $validated = $request->validate([ 'current_password' => 'required|string', 'new_password' => 'required|string|min:8|max:64', ]); // 선택적으로 확인 비밀번호가 온 경우 체크 if ($request->filled('new_password_confirmation') && $request->input('new_password_confirmation') !== $validated['new_password']) { return ['error' => '비밀번호 확인이 일치하지 않습니다.', 'code' => 400]; } // 유저 조회 $user = User::find($apiUserId); if (! $user) { return ['error' => '유저를 찾을 수 없음', 'code' => 404]; } // 현재 비밀번호 확인 if (! Hash::check($validated['current_password'], $user->password)) { return ['error' => '현재 비밀번호가 일치하지 않습니다.', 'code' => 400]; } // 기존 비밀번호와 동일한지 방지 if (Hash::check($validated['new_password'], $user->password)) { return ['error' => '새 비밀번호가 기존 비밀번호와 동일합니다.', 'code' => 400]; } // 비밀번호 변경 (guarded 우회: 직접 대입 + save) $user->password = Hash::make($validated['new_password']); $saved = $user->save(); // (선택) 모든 기존 토큰 무효화하려면 아래 주석 해제 // $user->tokens()->delete(); return 'success'; } /** * 나의 테넌트 목록 */ public static function getMyTenants() { $apiUser = app('api_user'); $data = UserTenant::join('tenants', 'user_tenants.tenant_id', '=', 'tenants.id') ->where('user_tenants.user_id', $apiUser) ->get([ 'tenants.id', 'tenants.company_name', 'user_tenants.is_active', 'user_tenants.is_default', ]); return $data; } /** * 나의 테넌트 전환 */ public static function switchMyTenant(int $tenantId) { $apiUser = app('api_user'); // 1) 현재 유저의 기본 테넌트를 모두 해제 UserTenant::withoutGlobalScopes() ->where('user_id', $apiUser) ->where('is_default', 1) ->update(['is_default' => 0]); // 2) 지정한 tenant_id를 기본 테넌트로 설정 $updated = UserTenant::withoutGlobalScopes() ->where('user_id', $apiUser) ->where('tenant_id', $tenantId) ->update(['is_default' => 1]); if (! $updated) { return ['error' => '해당 테넌트를 찾을 수 없습니다.', 'code' => 404]; } return 'success'; } /** * 로그인 사용자 정보 조회 (테넌트 + 메뉴 권한 포함) */ public static function getUserInfoForLogin(int $userId): array { // 1. 사용자 기본 정보 조회 $user = User::find($userId); if (! $user) { throw new \Exception('사용자를 찾을 수 없습니다.'); } // 기본 사용자 정보 (민감 정보 제외) $userInfo = [ 'id' => $user->id, 'user_id' => $user->user_id, 'name' => $user->name, 'email' => $user->email, 'phone' => $user->phone, 'department' => null, ]; // 2. 활성 테넌트 조회 (1순위: is_default=1, 2순위: is_active=1 첫 번째) $userTenants = UserTenant::with('tenant') ->where('user_id', $userId) ->where('is_active', 1) ->orderByDesc('is_default') ->orderBy('id') ->get(); if ($userTenants->isEmpty()) { return [ 'user' => $userInfo, 'tenant' => null, 'menus' => [], 'roles' => [], ]; } $defaultUserTenant = $userTenants->first(); $tenant = $defaultUserTenant->tenant; // 2-1. 소속 부서 조회 (tenant_user_profiles → departments) $profile = DB::table('tenant_user_profiles') ->where('user_id', $userId) ->where('tenant_id', $tenant->id) ->first(); if ($profile && $profile->department_id) { $dept = DB::table('departments')->where('id', $profile->department_id)->first(); if ($dept) { $userInfo['department'] = $dept->name; } } // 3. 테넌트 정보 구성 $tenantInfo = [ 'id' => $tenant->id, 'company_name' => $tenant->company_name, 'business_num' => $tenant->business_num, 'tenant_st_code' => $tenant->tenant_st_code, 'other_tenants' => $userTenants->skip(1)->map(function ($ut) { return [ 'tenant_id' => $ut->tenant_id, 'company_name' => $ut->tenant->company_name, 'business_num' => $ut->tenant->business_num, 'tenant_st_code' => $ut->tenant->tenant_st_code, ]; })->values()->toArray(), ]; // 4. 메뉴 권한 체크 (mng UserPermissionService와 동일한 로직) $now = now(); // 4-1. 역할 권한 (user_roles 테이블) $rolePermissions = DB::table('user_roles') ->join('role_has_permissions', 'user_roles.role_id', '=', 'role_has_permissions.role_id') ->join('permissions', 'role_has_permissions.permission_id', '=', 'permissions.id') ->where('user_roles.user_id', $userId) ->where('user_roles.tenant_id', $tenant->id) ->whereNull('user_roles.deleted_at') ->where('permissions.name', 'like', 'menu:%.view') ->pluck('permissions.name') ->toArray(); // 4-2. 부서 권한 (permission_overrides에서 Department 타입, effect=1) $deptPermissions = DB::table('department_user') ->join('permission_overrides', function ($join) use ($now) { $join->on('permission_overrides.model_id', '=', 'department_user.department_id') ->where('permission_overrides.model_type', '=', Department::class) ->whereNull('permission_overrides.deleted_at') ->where('permission_overrides.effect', 1) ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_from') ->orWhere('permission_overrides.effective_from', '<=', $now); }) ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_to') ->orWhere('permission_overrides.effective_to', '>=', $now); }); }) ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id') ->whereNull('department_user.deleted_at') ->where('department_user.user_id', $userId) ->where('department_user.tenant_id', $tenant->id) ->where('permission_overrides.tenant_id', $tenant->id) ->where('permissions.name', 'like', 'menu:%.view') ->pluck('permissions.name') ->toArray(); // 4-3. 개인 ALLOW 권한 (permission_overrides에서 User 타입, effect=1) // Note: mng는 App\Models\User를 사용하므로 하드코딩 $personalAllows = DB::table('permission_overrides') ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id') ->where('permission_overrides.model_type', 'App\\Models\\User') ->where('permission_overrides.model_id', $userId) ->where('permission_overrides.tenant_id', $tenant->id) ->where('permission_overrides.effect', 1) ->whereNull('permission_overrides.deleted_at') ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_from') ->orWhere('permission_overrides.effective_from', '<=', $now); }) ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_to') ->orWhere('permission_overrides.effective_to', '>=', $now); }) ->where('permissions.name', 'like', 'menu:%.view') ->pluck('permissions.name') ->toArray(); // 4-4. 개인 DENY 권한 (permission_overrides에서 User 타입, effect=0) // Note: mng는 App\Models\User를 사용하므로 하드코딩 $personalDenies = DB::table('permission_overrides') ->join('permissions', 'permissions.id', '=', 'permission_overrides.permission_id') ->where('permission_overrides.model_type', 'App\\Models\\User') ->where('permission_overrides.model_id', $userId) ->where('permission_overrides.tenant_id', $tenant->id) ->where('permission_overrides.effect', 0) ->whereNull('permission_overrides.deleted_at') ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_from') ->orWhere('permission_overrides.effective_from', '<=', $now); }) ->where(function ($q) use ($now) { $q->whereNull('permission_overrides.effective_to') ->orWhere('permission_overrides.effective_to', '>=', $now); }) ->where('permissions.name', 'like', 'menu:%.view') ->pluck('permissions.name') ->toArray(); // 4-5. 최종 권한 계산: (역할 OR 부서 OR 개인ALLOW) - 개인DENY $allAllowed = array_unique(array_merge($rolePermissions, $deptPermissions, $personalAllows)); $effectivePermissions = array_diff($allAllowed, $personalDenies); // 메뉴 ID 추출 $allowedMenuIds = []; foreach ($effectivePermissions as $permName) { if (preg_match('/^menu:(\d+)\.view$/', $permName, $matches)) { $allowedMenuIds[] = (int) $matches[1]; } } // 5. 메뉴 목록 조회 (권한 있는 메뉴만) $menus = []; if (! empty($allowedMenuIds)) { $menus = Menu::where('tenant_id', $tenant->id) ->where('is_active', 1) ->whereIn('id', $allowedMenuIds) ->orderBy('parent_id') ->orderBy('sort_order') ->get(['id', 'parent_id', 'name', 'url', 'icon', 'sort_order', 'is_external', 'external_url']) ->toArray(); } // 6. 역할(Role) 정보 조회 (user_roles 테이블 사용 - mng와 동일) $roles = DB::table('user_roles') ->join('roles', 'user_roles.role_id', '=', 'roles.id') ->where('user_roles.user_id', $userId) ->where('user_roles.tenant_id', $tenant->id) ->whereNull('user_roles.deleted_at') ->select('roles.id', 'roles.name', 'roles.description') ->get() ->toArray(); return [ 'user' => $userInfo, 'tenant' => $tenantInfo, 'menus' => $menus, 'roles' => $roles, ]; } }