203 lines
7.7 KiB
Dart
203 lines
7.7 KiB
Dart
import 'package:dio/dio.dart';
|
|
import '../../../../core/constants/api_constants.dart';
|
|
import '../../../../core/errors/exceptions.dart';
|
|
import '../../../../core/network/dio_client.dart';
|
|
import '../models/auth_response_model.dart';
|
|
import '../models/login_dto.dart';
|
|
import '../models/register_dto.dart';
|
|
import '../models/user_model.dart';
|
|
|
|
/// Remote data source for authentication operations
|
|
abstract class AuthRemoteDataSource {
|
|
/// Login user with email and password
|
|
Future<AuthResponseModel> login(LoginDto loginDto);
|
|
|
|
/// Register new user
|
|
Future<AuthResponseModel> register(RegisterDto registerDto);
|
|
|
|
/// Get current user profile
|
|
Future<UserModel> getProfile();
|
|
|
|
/// Refresh access token using refresh token
|
|
Future<AuthResponseModel> refreshToken(String refreshToken);
|
|
}
|
|
|
|
/// Implementation of AuthRemoteDataSource
|
|
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
|
final DioClient dioClient;
|
|
|
|
AuthRemoteDataSourceImpl({required this.dioClient});
|
|
|
|
@override
|
|
Future<AuthResponseModel> login(LoginDto loginDto) async {
|
|
try {
|
|
print('📡 DataSource: Calling login API...');
|
|
final response = await dioClient.post(
|
|
ApiConstants.login,
|
|
data: loginDto.toJson(),
|
|
);
|
|
|
|
print('📡 DataSource: Status=${response.statusCode}');
|
|
print('📡 DataSource: Response data keys=${response.data.keys.toList()}');
|
|
|
|
if (response.statusCode == ApiConstants.statusOk) {
|
|
// API returns nested structure: {success, data: {access_token, user}, message}
|
|
// Extract the 'data' object
|
|
final responseData = response.data['data'] as Map<String, dynamic>;
|
|
print('📡 DataSource: Extracted data object with keys=${responseData.keys.toList()}');
|
|
|
|
final authResponseModel = AuthResponseModel.fromJson(responseData);
|
|
print('📡 DataSource: Parsed successfully, token length=${authResponseModel.accessToken.length}');
|
|
return authResponseModel;
|
|
} else {
|
|
throw ServerException('Login failed with status: ${response.statusCode}');
|
|
}
|
|
} on DioException catch (e) {
|
|
print('❌ DataSource: DioException - ${e.message}');
|
|
throw _handleDioError(e);
|
|
} catch (e, stackTrace) {
|
|
print('❌ DataSource: Unexpected error - $e');
|
|
print('Stack trace: $stackTrace');
|
|
throw ServerException('Unexpected error during login: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<AuthResponseModel> register(RegisterDto registerDto) async {
|
|
try {
|
|
final response = await dioClient.post(
|
|
ApiConstants.register,
|
|
data: registerDto.toJson(),
|
|
);
|
|
|
|
if (response.statusCode == ApiConstants.statusCreated ||
|
|
response.statusCode == ApiConstants.statusOk) {
|
|
// API returns nested structure: {success, data: {access_token, user}, message}
|
|
// Extract the 'data' object
|
|
final responseData = response.data['data'] as Map<String, dynamic>;
|
|
return AuthResponseModel.fromJson(responseData);
|
|
} else {
|
|
throw ServerException('Registration failed with status: ${response.statusCode}');
|
|
}
|
|
} on DioException catch (e) {
|
|
throw _handleDioError(e);
|
|
} catch (e) {
|
|
throw ServerException('Unexpected error during registration: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<UserModel> getProfile() async {
|
|
try {
|
|
print('📡 DataSource: Calling getProfile API...');
|
|
final response = await dioClient.get(ApiConstants.profile);
|
|
|
|
print('📡 DataSource: Profile status=${response.statusCode}');
|
|
print('📡 DataSource: Profile response keys=${response.data?.keys?.toList()}');
|
|
print('📡 DataSource: Profile response=$response.data}');
|
|
|
|
if (response.statusCode == ApiConstants.statusOk) {
|
|
// API returns nested structure: {success, data: user, message}
|
|
// Extract the 'data' object
|
|
final userData = response.data['data'] as Map<String, dynamic>;
|
|
print('📡 DataSource: Extracted user data with keys=${userData.keys.toList()}');
|
|
|
|
final userModel = UserModel.fromJson(userData);
|
|
print('📡 DataSource: User parsed successfully: ${userModel.name}');
|
|
return userModel;
|
|
} else {
|
|
throw ServerException('Get profile failed with status: ${response.statusCode}');
|
|
}
|
|
} on DioException catch (e) {
|
|
print('❌ DataSource: Profile DioException - ${e.message}');
|
|
throw _handleDioError(e);
|
|
} catch (e, stackTrace) {
|
|
print('❌ DataSource: Profile unexpected error - $e');
|
|
print('Stack trace: $stackTrace');
|
|
throw ServerException('Unexpected error getting profile: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<AuthResponseModel> refreshToken(String refreshToken) async {
|
|
try {
|
|
print('📡 DataSource: Calling refresh token API...');
|
|
final response = await dioClient.post(
|
|
ApiConstants.refreshToken,
|
|
data: {'refreshToken': refreshToken},
|
|
);
|
|
|
|
if (response.statusCode == ApiConstants.statusOk) {
|
|
// API returns nested structure: {success, data: {access_token, refresh_token, user}, message}
|
|
// Extract the 'data' object
|
|
final responseData = response.data['data'] as Map<String, dynamic>;
|
|
print('📡 DataSource: Token refreshed successfully');
|
|
return AuthResponseModel.fromJson(responseData);
|
|
} else {
|
|
throw ServerException('Token refresh failed with status: ${response.statusCode}');
|
|
}
|
|
} on DioException catch (e) {
|
|
print('❌ DataSource: Refresh token failed - ${e.message}');
|
|
throw _handleDioError(e);
|
|
} catch (e) {
|
|
print('❌ DataSource: Unexpected error refreshing token: $e');
|
|
throw ServerException('Unexpected error refreshing token: $e');
|
|
}
|
|
}
|
|
|
|
/// Handle Dio errors and convert to custom exceptions
|
|
Exception _handleDioError(DioException error) {
|
|
switch (error.type) {
|
|
case DioExceptionType.connectionTimeout:
|
|
case DioExceptionType.sendTimeout:
|
|
case DioExceptionType.receiveTimeout:
|
|
return NetworkException('Connection timeout. Please check your internet connection.');
|
|
|
|
case DioExceptionType.badResponse:
|
|
final statusCode = error.response?.statusCode;
|
|
final message = error.response?.data?['message'] ?? error.message;
|
|
|
|
switch (statusCode) {
|
|
case ApiConstants.statusUnauthorized:
|
|
return InvalidCredentialsException(message ?? 'Invalid email or password');
|
|
|
|
case ApiConstants.statusForbidden:
|
|
return UnauthorizedException(message ?? 'Access forbidden');
|
|
|
|
case ApiConstants.statusNotFound:
|
|
return NotFoundException(message ?? 'Resource not found');
|
|
|
|
case ApiConstants.statusUnprocessableEntity:
|
|
return ValidationException(message ?? 'Validation failed');
|
|
|
|
case 409: // Conflict
|
|
return ConflictException(message ?? 'Email already exists');
|
|
|
|
case ApiConstants.statusTooManyRequests:
|
|
return ServerException('Too many requests. Please try again later.');
|
|
|
|
case ApiConstants.statusInternalServerError:
|
|
case ApiConstants.statusBadGateway:
|
|
case ApiConstants.statusServiceUnavailable:
|
|
case ApiConstants.statusGatewayTimeout:
|
|
return ServerException(message ?? 'Server error. Please try again later.');
|
|
|
|
default:
|
|
return ServerException(message ?? 'Unknown error occurred');
|
|
}
|
|
|
|
case DioExceptionType.connectionError:
|
|
return NetworkException('No internet connection. Please check your network.');
|
|
|
|
case DioExceptionType.badCertificate:
|
|
return NetworkException('SSL certificate error');
|
|
|
|
case DioExceptionType.cancel:
|
|
return NetworkException('Request was cancelled');
|
|
|
|
default:
|
|
return ServerException(error.message ?? 'Unknown error occurred');
|
|
}
|
|
}
|
|
}
|