import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/errors/exceptions.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/models/user/user_dto.dart'; /// 사용자 원격 데이터 소스 (서버 API v0.2.1 대응) /// 엔드포인트: /api/v1/users abstract class UserRemoteDataSource { /// 사용자 목록 조회 (페이지네이션 지원) Future getUsers({ int page = 1, int perPage = 20, bool? isActive, String? role, }); /// 단일 사용자 조회 Future getUser(int id); /// 사용자 생성 Future createUser(UserRequestDto request); /// 사용자 정보 수정 (비밀번호 포함) Future updateUser(int id, UserUpdateRequestDto request); /// 사용자 소프트 삭제 (is_active = false) Future deleteUser(int id); /// 사용자명 중복 확인 Future checkUsernameAvailability(String username); } @LazySingleton(as: UserRemoteDataSource) class UserRemoteDataSourceImpl implements UserRemoteDataSource { final ApiClient _apiClient; UserRemoteDataSourceImpl(this._apiClient); /// 사용자 목록 조회 (서버 API v0.2.1 대응) @override Future getUsers({ int page = 1, int perPage = 20, bool? isActive, String? role, }) async { try { final queryParams = { 'page': page, 'per_page': perPage, }; // 필터 파라미터 추가 (서버에서 지원하는 것만) if (isActive != null) { queryParams['is_active'] = isActive; } if (role != null) { queryParams['role'] = role; } final response = await _apiClient.get( '/users', queryParameters: queryParams, ); // 백엔드 API v0.2.1 실제 응답 형식 처리 (success: true) if (response.data != null && response.data['success'] == true && response.data['data'] != null) { final data = response.data['data']; final paginationData = response.data['pagination']; // 배열 응답 + 페이지네이션 정보 if (data is List && paginationData is Map) { return UserListDto( users: data.map((json) => UserDto.fromJson(json)).toList(), total: paginationData['total'] ?? data.length, page: paginationData['page'] ?? page, perPage: paginationData['per_page'] ?? perPage, totalPages: paginationData['total_pages'] ?? 1, ); } // 기존 구조 호환성 유지 else if (data is Map && data['users'] != null) { return UserListDto.fromJson(Map.from(data)); } // 단순 배열 응답인 경우 else if (data is List) { return UserListDto( users: data.map((json) => UserDto.fromJson(json)).toList(), total: data.length, page: page, perPage: perPage, totalPages: 1, ); } else { throw ApiException( message: 'Unexpected response format for user list', statusCode: response.statusCode, ); } } else { throw ApiException( message: response.data?['message'] ?? '사용자 목록을 불러오는데 실패했습니다', statusCode: response.statusCode, ); } } on DioException catch (e) { throw ApiException( message: e.response?.data?['message'] ?? e.message ?? '사용자 목록을 불러오는데 실패했습니다', statusCode: e.response?.statusCode, ); } } /// 단일 사용자 조회 (GET /api/v1/users/{id}) @override Future getUser(int id) async { try { final response = await _apiClient.get('/users/$id'); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return UserDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['message'] ?? '사용자 정보를 불러오는데 실패했습니다', statusCode: response.statusCode, ); } } on DioException catch (e) { throw ApiException( message: e.response?.data?['message'] ?? e.message ?? '사용자 정보를 불러오는데 실패했습니다', statusCode: e.response?.statusCode, ); } } /// 사용자 생성 (POST /api/v1/users) @override Future createUser(UserRequestDto request) async { try { final response = await _apiClient.post( '/users', data: request.toJson(), ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return UserDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['message'] ?? '사용자 생성에 실패했습니다', statusCode: response.statusCode, ); } } on DioException catch (e) { throw ApiException( message: e.response?.data?['message'] ?? e.message ?? '사용자 생성에 실패했습니다', statusCode: e.response?.statusCode, ); } } /// 사용자 수정 (PUT /api/v1/users/{id}) @override Future updateUser(int id, UserUpdateRequestDto request) async { try { // null이나 빈 값 필터링하여 실제로 변경된 필드만 전송 final requestData = request.toJson(); requestData.removeWhere((key, value) => value == null || (value is String && value.isEmpty)); final response = await _apiClient.put( '/users/$id', data: requestData, ); if (response.data != null && response.data['success'] == true && response.data['data'] != null) { return UserDto.fromJson(response.data['data']); } else { throw ApiException( message: response.data?['message'] ?? '사용자 정보 수정에 실패했습니다', statusCode: response.statusCode, ); } } on DioException catch (e) { throw ApiException( message: e.response?.data?['message'] ?? e.message ?? '사용자 정보 수정에 실패했습니다', statusCode: e.response?.statusCode, ); } } /// 사용자 소프트 삭제 (DELETE /api/v1/users/{id}) /// 서버에서 is_active = false로 설정 @override Future deleteUser(int id) async { try { final response = await _apiClient.delete('/users/$id'); // 소프트 딜리트는 응답 데이터가 있을 수 있음 if (response.statusCode != null && response.statusCode! >= 400) { throw ApiException( message: response.data?['message'] ?? '사용자 삭제에 실패했습니다', statusCode: response.statusCode, ); } } on DioException catch (e) { throw ApiException( message: e.response?.data?['message'] ?? e.message ?? '사용자 삭제에 실패했습니다', statusCode: e.response?.statusCode, ); } } /// 사용자명 중복 확인 (구현 예정 - 현재 서버에서 미지원) /// TODO: 서버 API에 해당 엔드포인트 추가되면 구현 @override Future checkUsernameAvailability(String username) async { try { // 임시로 POST 시도를 통한 중복 체크 // 실제 서버에 해당 엔드포인트가 없다면 항상 available = true 반환 return const CheckUsernameResponse( available: true, message: 'Username availability check not implemented in server', ); } catch (e) { return const CheckUsernameResponse( available: false, message: 'Username availability check failed', ); } } }