update api
This commit is contained in:
159
lib/features/auth/data/datasources/auth_remote_datasource.dart
Normal file
159
lib/features/auth/data/datasources/auth_remote_datasource.dart
Normal file
@@ -0,0 +1,159 @@
|
||||
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
|
||||
Future<AuthResponseModel> refreshToken();
|
||||
}
|
||||
|
||||
/// Implementation of AuthRemoteDataSource
|
||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
final DioClient dioClient;
|
||||
|
||||
AuthRemoteDataSourceImpl({required this.dioClient});
|
||||
|
||||
@override
|
||||
Future<AuthResponseModel> login(LoginDto loginDto) async {
|
||||
try {
|
||||
final response = await dioClient.post(
|
||||
ApiConstants.login,
|
||||
data: loginDto.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == ApiConstants.statusOk) {
|
||||
return AuthResponseModel.fromJson(response.data);
|
||||
} else {
|
||||
throw ServerException('Login failed with status: ${response.statusCode}');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioError(e);
|
||||
} catch (e) {
|
||||
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) {
|
||||
return AuthResponseModel.fromJson(response.data);
|
||||
} 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 {
|
||||
final response = await dioClient.get(ApiConstants.profile);
|
||||
|
||||
if (response.statusCode == ApiConstants.statusOk) {
|
||||
return UserModel.fromJson(response.data);
|
||||
} else {
|
||||
throw ServerException('Get profile failed with status: ${response.statusCode}');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioError(e);
|
||||
} catch (e) {
|
||||
throw ServerException('Unexpected error getting profile: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthResponseModel> refreshToken() async {
|
||||
try {
|
||||
final response = await dioClient.post(ApiConstants.refreshToken);
|
||||
|
||||
if (response.statusCode == ApiConstants.statusOk) {
|
||||
return AuthResponseModel.fromJson(response.data);
|
||||
} else {
|
||||
throw ServerException('Token refresh failed with status: ${response.statusCode}');
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw _handleDioError(e);
|
||||
} catch (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');
|
||||
}
|
||||
}
|
||||
}
|
||||
42
lib/features/auth/data/models/auth_response_model.dart
Normal file
42
lib/features/auth/data/models/auth_response_model.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import '../../domain/entities/auth_response.dart';
|
||||
import 'user_model.dart';
|
||||
|
||||
/// AuthResponse model for data layer (extends AuthResponse entity)
|
||||
class AuthResponseModel extends AuthResponse {
|
||||
const AuthResponseModel({
|
||||
required super.accessToken,
|
||||
required super.user,
|
||||
});
|
||||
|
||||
/// Create AuthResponseModel from JSON
|
||||
factory AuthResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
return AuthResponseModel(
|
||||
accessToken: json['access_token'] as String,
|
||||
user: UserModel.fromJson(json['user'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert AuthResponseModel to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'access_token': accessToken,
|
||||
'user': (user as UserModel).toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create AuthResponseModel from AuthResponse entity
|
||||
factory AuthResponseModel.fromEntity(AuthResponse authResponse) {
|
||||
return AuthResponseModel(
|
||||
accessToken: authResponse.accessToken,
|
||||
user: authResponse.user,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to AuthResponse entity
|
||||
AuthResponse toEntity() {
|
||||
return AuthResponse(
|
||||
accessToken: accessToken,
|
||||
user: user,
|
||||
);
|
||||
}
|
||||
}
|
||||
18
lib/features/auth/data/models/login_dto.dart
Normal file
18
lib/features/auth/data/models/login_dto.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
/// Login request Data Transfer Object
|
||||
class LoginDto {
|
||||
final String email;
|
||||
final String password;
|
||||
|
||||
const LoginDto({
|
||||
required this.email,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
/// Convert to JSON for API request
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'email': email,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
}
|
||||
24
lib/features/auth/data/models/register_dto.dart
Normal file
24
lib/features/auth/data/models/register_dto.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
/// Register request Data Transfer Object
|
||||
class RegisterDto {
|
||||
final String name;
|
||||
final String email;
|
||||
final String password;
|
||||
final List<String> roles;
|
||||
|
||||
const RegisterDto({
|
||||
required this.name,
|
||||
required this.email,
|
||||
required this.password,
|
||||
this.roles = const ['user'],
|
||||
});
|
||||
|
||||
/// Convert to JSON for API request
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'email': email,
|
||||
'password': password,
|
||||
'roles': roles,
|
||||
};
|
||||
}
|
||||
}
|
||||
66
lib/features/auth/data/models/user_model.dart
Normal file
66
lib/features/auth/data/models/user_model.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import '../../domain/entities/user.dart';
|
||||
|
||||
/// User model for data layer (extends User entity)
|
||||
class UserModel extends User {
|
||||
const UserModel({
|
||||
required super.id,
|
||||
required super.name,
|
||||
required super.email,
|
||||
required super.roles,
|
||||
required super.isActive,
|
||||
required super.createdAt,
|
||||
required super.updatedAt,
|
||||
});
|
||||
|
||||
/// Create UserModel from JSON
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||
return UserModel(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String,
|
||||
roles: (json['roles'] as List<dynamic>).cast<String>(),
|
||||
isActive: json['isActive'] as bool? ?? true,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert UserModel to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'roles': roles,
|
||||
'isActive': isActive,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create UserModel from User entity
|
||||
factory UserModel.fromEntity(User user) {
|
||||
return UserModel(
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
roles: user.roles,
|
||||
isActive: user.isActive,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to User entity
|
||||
User toEntity() {
|
||||
return User(
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
roles: roles,
|
||||
isActive: isActive,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
175
lib/features/auth/data/repositories/auth_repository_impl.dart
Normal file
175
lib/features/auth/data/repositories/auth_repository_impl.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/errors/exceptions.dart';
|
||||
import '../../../../core/errors/failures.dart';
|
||||
import '../../../../core/network/dio_client.dart';
|
||||
import '../../../../core/storage/secure_storage.dart';
|
||||
import '../../domain/entities/auth_response.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../datasources/auth_remote_datasource.dart';
|
||||
import '../models/login_dto.dart';
|
||||
import '../models/register_dto.dart';
|
||||
|
||||
/// Implementation of AuthRepository
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
final SecureStorage secureStorage;
|
||||
final DioClient dioClient;
|
||||
|
||||
AuthRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.secureStorage,
|
||||
required this.dioClient,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthResponse>> login({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
final loginDto = LoginDto(email: email, password: password);
|
||||
final authResponse = await remoteDataSource.login(loginDto);
|
||||
|
||||
// Save token to secure storage
|
||||
await secureStorage.saveAccessToken(authResponse.accessToken);
|
||||
|
||||
// Set token in Dio client for subsequent requests
|
||||
dioClient.setAuthToken(authResponse.accessToken);
|
||||
|
||||
return Right(authResponse);
|
||||
} on InvalidCredentialsException catch (e) {
|
||||
return Left(InvalidCredentialsFailure(e.message));
|
||||
} on UnauthorizedException catch (e) {
|
||||
return Left(UnauthorizedFailure(e.message));
|
||||
} on ValidationException catch (e) {
|
||||
return Left(ValidationFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('Unexpected error: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthResponse>> register({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
List<String> roles = const ['user'],
|
||||
}) async {
|
||||
try {
|
||||
final registerDto = RegisterDto(
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
roles: roles,
|
||||
);
|
||||
final authResponse = await remoteDataSource.register(registerDto);
|
||||
|
||||
// Save token to secure storage
|
||||
await secureStorage.saveAccessToken(authResponse.accessToken);
|
||||
|
||||
// Set token in Dio client for subsequent requests
|
||||
dioClient.setAuthToken(authResponse.accessToken);
|
||||
|
||||
return Right(authResponse);
|
||||
} on ConflictException catch (e) {
|
||||
return Left(ConflictFailure(e.message));
|
||||
} on ValidationException catch (e) {
|
||||
return Left(ValidationFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('Unexpected error: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, User>> getProfile() async {
|
||||
try {
|
||||
final user = await remoteDataSource.getProfile();
|
||||
return Right(user);
|
||||
} on UnauthorizedException catch (e) {
|
||||
return Left(UnauthorizedFailure(e.message));
|
||||
} on TokenExpiredException catch (e) {
|
||||
return Left(TokenExpiredFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('Unexpected error: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AuthResponse>> refreshToken() async {
|
||||
try {
|
||||
final authResponse = await remoteDataSource.refreshToken();
|
||||
|
||||
// Update token in secure storage
|
||||
await secureStorage.saveAccessToken(authResponse.accessToken);
|
||||
|
||||
// Update token in Dio client
|
||||
dioClient.setAuthToken(authResponse.accessToken);
|
||||
|
||||
return Right(authResponse);
|
||||
} on UnauthorizedException catch (e) {
|
||||
return Left(UnauthorizedFailure(e.message));
|
||||
} on TokenExpiredException catch (e) {
|
||||
return Left(TokenExpiredFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('Unexpected error: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, void>> logout() async {
|
||||
try {
|
||||
// Clear token from secure storage
|
||||
await secureStorage.deleteAllTokens();
|
||||
|
||||
// Clear token from Dio client
|
||||
dioClient.clearAuthToken();
|
||||
|
||||
return const Right(null);
|
||||
} catch (e) {
|
||||
return Left(CacheFailure('Failed to logout: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isAuthenticated() async {
|
||||
try {
|
||||
final hasToken = await secureStorage.hasAccessToken();
|
||||
if (hasToken) {
|
||||
final token = await secureStorage.getAccessToken();
|
||||
if (token != null) {
|
||||
dioClient.setAuthToken(token);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getAccessToken() async {
|
||||
try {
|
||||
return await secureStorage.getAccessToken();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user