import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; import 'package:superport/core/errors/exceptions.dart'; import 'package:superport/core/errors/failures.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/models/auth/login_request.dart'; import 'package:superport/data/models/auth/login_response.dart'; import 'package:superport/data/models/auth/logout_request.dart'; import 'package:superport/data/models/auth/refresh_token_request.dart'; import 'package:superport/data/models/auth/token_response.dart'; import 'package:superport/core/utils/debug_logger.dart'; abstract class AuthRemoteDataSource { Future> login(LoginRequest request); Future> logout(LogoutRequest request); Future> refreshToken(RefreshTokenRequest request); } @LazySingleton(as: AuthRemoteDataSource) class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { final ApiClient _apiClient; AuthRemoteDataSourceImpl(this._apiClient); @override Future> login(LoginRequest request) async { try { DebugLogger.logLogin('요청 시작', data: request.toJson()); final response = await _apiClient.post( '/auth/login', data: request.toJson(), ); DebugLogger.logApiResponse( url: '/auth/login', statusCode: response.statusCode, data: response.data, ); if (response.statusCode == 200 && response.data != null) { final responseData = response.data; // API 응답 형식 확인 및 처리 // 형식 1: { success: bool, data: {...} } if (responseData is Map && responseData['success'] == true && responseData['data'] != null) { DebugLogger.logLogin('응답 형식 1 감지', data: {'format': 'wrapped'}); // 응답 데이터 구조 검증 final dataFields = responseData['data'] as Map; DebugLogger.validateResponseStructure( dataFields, ['accessToken', 'refreshToken', 'user'], responseName: 'LoginResponse.data', ); final loginResponse = DebugLogger.parseJsonWithLogging( responseData['data'], LoginResponse.fromJson, objectName: 'LoginResponse', ); if (loginResponse != null) { DebugLogger.logLogin('로그인 성공', data: { 'userId': loginResponse.user.id, 'userEmail': loginResponse.user.email, 'userRole': loginResponse.user.role, }); return Right(loginResponse); } else { return Left(ServerFailure(message: 'LoginResponse 파싱 실패')); } } // 형식 2: 직접 LoginResponse 형태 else if (responseData is Map && (responseData.containsKey('accessToken') || responseData.containsKey('access_token'))) { DebugLogger.logLogin('응답 형식 2 감지', data: {'format': 'direct'}); // 응답 데이터 구조 검증 DebugLogger.validateResponseStructure( responseData as Map, ['accessToken', 'refreshToken', 'user'], responseName: 'LoginResponse', ); final loginResponse = DebugLogger.parseJsonWithLogging( responseData, LoginResponse.fromJson, objectName: 'LoginResponse', ); if (loginResponse != null) { DebugLogger.logLogin('로그인 성공', data: { 'userId': loginResponse.user.id, 'userEmail': loginResponse.user.email, 'userRole': loginResponse.user.role, }); return Right(loginResponse); } else { return Left(ServerFailure(message: 'LoginResponse 파싱 실패')); } } // 그 외의 경우 else { DebugLogger.logError( '알 수 없는 응답 형식', additionalData: { 'responseKeys': responseData.keys.toList(), 'responseType': responseData.runtimeType.toString(), }, ); return Left(ServerFailure( message: '잘못된 응답 형식입니다.', )); } } else { DebugLogger.logError( '비정상적인 응답', additionalData: { 'statusCode': response.statusCode, 'hasData': response.data != null, }, ); return Left(ServerFailure( message: response.statusMessage ?? '로그인 실패', )); } } on DioException catch (e) { DebugLogger.logError( 'DioException 발생', error: e, additionalData: { 'type': e.type.toString(), 'message': e.message, 'error': e.error?.toString(), 'statusCode': e.response?.statusCode, }, ); // ErrorInterceptor에서 변환된 예외 처리 if (e.error is NetworkException) { return Left(NetworkFailure(message: (e.error as NetworkException).message)); } else if (e.error is UnauthorizedException) { return Left(AuthenticationFailure(message: (e.error as UnauthorizedException).message)); } else if (e.error is ServerException) { return Left(ServerFailure(message: (e.error as ServerException).message)); } // 기본 DioException 처리 if (e.response?.statusCode == 401) { return Left(AuthenticationFailure( message: '이메일 또는 비밀번호가 올바르지 않습니다.', )); } // 네트워크 관련 에러 처리 if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.sendTimeout || e.type == DioExceptionType.receiveTimeout) { return Left(ServerFailure(message: '네트워크 연결 시간이 초과되었습니다. 로그인 중 오류가 발생했습니다.')); } return Left(ServerFailure(message: e.message ?? '로그인 중 오류가 발생했습니다.')); } catch (e, stackTrace) { DebugLogger.logError( '예상치 못한 예외 발생', error: e, stackTrace: stackTrace, ); return Left(ServerFailure(message: '로그인 중 오류가 발생했습니다.')); } } @override Future> logout(LogoutRequest request) async { try { final response = await _apiClient.post( '/auth/logout', data: request.toJson(), ); if (response.statusCode == 200) { // API 응답이 { success: bool, data: {...} } 형태인지 확인 if (response.data is Map && response.data['success'] == true) { return const Right(null); } else if (response.data == null) { // 응답 본문이 없는 경우도 성공으로 처리 return const Right(null); } else { return Left(ServerFailure( message: '로그아웃 실패', )); } } else { return Left(ServerFailure( message: response.statusMessage ?? '로그아웃 실패', )); } } catch (e) { if (e is ApiException) { return Left(ServerFailure(message: e.message)); } return Left(ServerFailure(message: '로그아웃 중 오류가 발생했습니다.')); } } @override Future> refreshToken(RefreshTokenRequest request) async { try { final response = await _apiClient.post( '/auth/refresh', data: request.toJson(), ); if (response.statusCode == 200 && response.data != null) { // API 응답이 { success: bool, data: {...} } 형태인 경우 final responseData = response.data; if (responseData is Map && responseData['success'] == true && responseData['data'] != null) { final tokenResponse = TokenResponse.fromJson(responseData['data']); return Right(tokenResponse); } else { return Left(ServerFailure( message: '잘못된 응답 형식입니다.', )); } } else { return Left(ServerFailure( message: response.statusMessage ?? '토큰 갱신 실패', )); } } catch (e) { if (e is ApiException) { if (e.statusCode == 401) { return Left(AuthenticationFailure( message: '인증이 만료되었습니다. 다시 로그인해주세요.', )); } return Left(ServerFailure(message: e.message)); } return Left(ServerFailure(message: '토큰 갱신 중 오류가 발생했습니다.')); } } }