Skip to content

Commit

Permalink
Create domain exceptions (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
banghuazhao authored Nov 11, 2024
1 parent 22c184d commit d72d090
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 90 deletions.
17 changes: 0 additions & 17 deletions data/lib/core/exceptions.dart

This file was deleted.

5 changes: 2 additions & 3 deletions data/lib/data_sources/authenticated_http_client.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// lib/data/datasources/authenticated_http_client.dart

import 'package:domain/entities/domain_exceptions.dart';
import 'package:domain/repositories_abstract/token_provider.dart';
import 'package:http/http.dart' as http;

import '../core/exceptions.dart';

class AuthenticatedHttpClient extends http.BaseClient {
final http.Client _inner;
final TokenProvider _tokenProvider;
Expand All @@ -25,7 +24,7 @@ class AuthenticatedHttpClient extends http.BaseClient {
await _tokenProvider.deleteToken();

// Notify the app or handle as needed
throw UnauthenticatedException('Token expired');
throw UnauthorizedException('Token expired');
}

return response;
Expand Down
32 changes: 32 additions & 0 deletions data/lib/mappers/domain_exception_mapper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'dart:convert';

import 'package:domain/entities/domain_exceptions.dart';
import 'package:http/http.dart';

/// Helper function to map server errors to domain-specific exceptions
DomainException mapServerErrorToDomainException(Response response) {
final statusCode = response.statusCode;
final responseData = jsonDecode(response.body);
final message = responseData['message'];

switch (statusCode) {
case 400:
return BadRequestException(message ?? 'Bad Request');
case 401:
return UnauthorizedException(message ?? 'Unauthorized');
case 403:
return ForbiddenException(message ?? 'Forbidden');
case 404:
return NotFoundException(message ?? 'Not Found');
case 409:
return ResourceAlreadyExistsException(message ?? 'Resource already exists');
case 422:
return UnprocessableEntityException(message ?? 'Unprocessable Entity');
case 429:
return TooManyRequestsException(message ?? 'Too Many Requests');
case 500:
return InternalServerErrorException(message ?? 'Internal Server Error');
default:
return InternalServerErrorException(message ?? 'Server error with status code $statusCode');
}
}
97 changes: 43 additions & 54 deletions data/lib/repositories/auth_repository_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import 'dart:convert';

import 'package:data/mappers/domain_exception_mapper.dart';
import 'package:domain/entities/domain_exceptions.dart';
import 'package:domain/entities/user.dart';
import 'package:domain/repositories_abstract/auth_repository.dart';
import 'package:http/http.dart' as http;

import '../core/exceptions.dart';
import '../data_sources/authenticated_http_client.dart';

class AuthRepositoryImpl implements AuthRepository {
Expand All @@ -16,18 +17,27 @@ class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({required this.client, required this.authClient});

@override
Future<User> signup(String email, String password, String verificationCode, {String? name}) async {
Future<User> signup(String email, String password, String verificationCode,
{String? name}) async {
final url = Uri.parse('http://localhost:3000/api/users/');
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(
{'email': email, 'password': password,
'verificationCode': verificationCode, if (name != null) 'name': name,},),
{
'email': email,
'password': password,
'verificationCode': verificationCode,
if (name != null) 'name': name,
},
),
);

if (response.statusCode == 200 || response.statusCode == 201) {
return User(email: email, name: name,);
return User(
email: email,
name: name,
);
} else {
throw Exception('Failed to sign up. Status code: ${response.statusCode}');
}
Expand All @@ -39,7 +49,10 @@ class AuthRepositoryImpl implements AuthRepository {
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email, 'password': password,}),
body: jsonEncode({
'email': email,
'password': password,
}),
);

if (response.statusCode == 200) {
Expand All @@ -62,9 +75,7 @@ class AuthRepositoryImpl implements AuthRepository {
if (response.statusCode == 200) {
return;
} else {
// Handle error responses
throw ServerException(
'Logout failed with status code: ${response.statusCode}');
throw mapServerErrorToDomainException(response);
}
}

Expand All @@ -78,11 +89,7 @@ class AuthRepositoryImpl implements AuthRepository {
);

if (response.statusCode != 200) {
// Handle error responses from the server
final responseData = jsonDecode(response.body);
final errorMessage =
responseData['message'] ?? 'Failed to send reset email';
throw ServerException(errorMessage);
throw mapServerErrorToDomainException(response);
}
// If statusCode is 200, assume the request was successful
} catch (error) {
Expand All @@ -91,9 +98,9 @@ class AuthRepositoryImpl implements AuthRepository {
}
}

Future<String> resetPassword(String email, String newPassword, String confirmationCode) async {
final url =
Uri.parse('http://localhost:3000/api/auth/reset-password');
Future<String> resetPassword(
String email, String newPassword, String confirmationCode) async {
final url = Uri.parse('http://localhost:3000/api/auth/reset-password');
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
Expand All @@ -108,62 +115,44 @@ class AuthRepositoryImpl implements AuthRepository {
final data = jsonDecode(response.body);
return data['message'];
} else {
throw ServerException(
'Password reset failed with status code: ${response.statusCode}');
throw mapServerErrorToDomainException(response);
}
}

Future<void> sendSignupVerificationCode(String email) async {
final url =
Uri.parse('http://localhost:3000/api/auth/send-verification');
try {
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email}),
);
final url = Uri.parse('http://localhost:3000/api/auth/send-verification');

if (response.statusCode == 200) {
// Request was successful, exit the function
return;
}
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'email': email}),
);

// Decode the server response to retrieve the error message
final responseData = jsonDecode(response.body);
final errorMessage = responseData['message'] ?? 'Failed to send verification email';

// Throw specific error based on the status code
if (response.statusCode == 400) {
throw ServerException('Invalid email address');
} else if (response.statusCode == 409) {
throw ServerException('Email is already registered');
} else {
throw ServerException(errorMessage);
}
// If statusCode is 200, assume the request was successful
} catch (error) {
// Re-throw network or parsing errors as custom exceptions
throw Exception('An error occurred. Please try again.');
if (response.statusCode == 200) {
// Request was successful, exit the function
return;
} else if (response.statusCode == 400) {
throw BadRequestException('Invalid email address');
} else if (response.statusCode == 409) {
throw ResourceAlreadyExistsException('Email is already registered');
} else {
throw mapServerErrorToDomainException(response);
}
}

Future<String> updatePassword(String newPassword) async {
final url =
Uri.parse('http://localhost:3000/api/auth/update-password');
final url = Uri.parse('http://localhost:3000/api/auth/update-password');
final response = await authClient.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'password': newPassword
}),
body: jsonEncode({'password': newPassword}),
);

if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['message'];
} else {
throw ServerException(
'Password update failed with status code: ${response.statusCode}');
throw mapServerErrorToDomainException(response);
}
}
}
41 changes: 41 additions & 0 deletions domain/lib/entities/domain_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// lib/domain/exceptions/domain_exceptions.dart
abstract class DomainException implements Exception {
final String message;
DomainException(this.message);
@override
String toString() => '$runtimeType: $message';
}

class BadRequestException extends DomainException {
BadRequestException([String message = 'Bad Request']) : super(message);
}

class UnauthorizedException extends DomainException {
UnauthorizedException([String message = 'Unauthorized']) : super(message);
}

class ForbiddenException extends DomainException {
ForbiddenException([String message = 'Forbidden']) : super(message);
}

class NotFoundException extends DomainException {
NotFoundException([String message = 'Not Found']) : super(message);
}

class ResourceAlreadyExistsException extends DomainException {
ResourceAlreadyExistsException([String message = 'Resource already exists']) : super(message);
}

class UnprocessableEntityException extends DomainException {
UnprocessableEntityException([String message = 'Unprocessable Entity']) : super(message);
}

class TooManyRequestsException extends DomainException {
TooManyRequestsException([String message = 'Too Many Requests']) : super(message);
}

class InternalServerErrorException extends DomainException {
InternalServerErrorException([String message = 'Internal Server Error']) : super(message);
}

// Add additional exceptions here
23 changes: 9 additions & 14 deletions lib/presentation/settings/viewModels/signup_view_model.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
// lib/presentation/viewmodels/signup_view_model.dart

import 'package:domain/entities/domain_exceptions.dart';
import 'package:domain/entities/user.dart';
import 'package:flutter/material.dart';
import 'package:domain/usecases/auth_usecase.dart';


class SignupViewModel extends ChangeNotifier {
final AuthUseCase authUseCase;

bool obscureTextNewPassword = true;
bool obscureTextConfirmPassword = true;
bool isSignUp = false;


void toggleNewPasswordVisibility() {
obscureTextNewPassword = !obscureTextNewPassword;
notifyListeners(); // Notify the UI about the change
Expand All @@ -23,22 +22,25 @@ class SignupViewModel extends ChangeNotifier {
notifyListeners(); // Notify the UI about the change
}


SignupViewModel({required this.authUseCase});

bool _isLoading = false;

bool get isLoading => _isLoading;

String? _errorMessage;

String? get errorMessage => _errorMessage;

Future<User?> signup(String email, String password,String verificationCode, {String? name}) async {
Future<User?> signup(String email, String password, String verificationCode,
{String? name}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();

try {
User user = await authUseCase.signup(email, password, verificationCode, name: name);
User user = await authUseCase.signup(email, password, verificationCode,
name: name);
return user;
} catch (e) {
_errorMessage = 'Signup failed: ${e.toString()}';
Expand All @@ -49,7 +51,7 @@ class SignupViewModel extends ChangeNotifier {
}
}

Future<void> continueSignUp(String email) async {
Future<void> signUpFor(String email) async {
_setLoadingState(true);
_errorMessage = '';

Expand All @@ -60,14 +62,7 @@ class SignupViewModel extends ChangeNotifier {
} catch (error) {
final errorMessage = error.toString();
print("Error Message: $errorMessage");

if (errorMessage.contains("already registered")) {
_errorMessage = 'Email is already registered';
} else if (errorMessage.contains("Invalid email")) {
_errorMessage = 'Invalid email address';
} else {
_errorMessage = 'Failed to send verification code.';
}
_errorMessage = errorMessage;
} finally {
_setLoadingState(false);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/presentation/settings/views/forget_password_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../login/login_button.dart';
import '../widgets/login_button.dart';
import '../viewModels/forget_password_view_model.dart'; // Adjust the import path as necessary
import '../../../injection_container.dart'; // Import your service locator to inject dependencies

Expand Down
2 changes: 1 addition & 1 deletion lib/presentation/settings/views/sigup_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ class _SignupFormState extends State<SignupForm> {
? null
: () async {
if (_formKey.currentState!.validate()) {
await viewModel.continueSignUp(email);
await viewModel.signUpFor(email);

if (viewModel.errorMessage != null && viewModel.errorMessage!.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
Expand Down

0 comments on commit d72d090

Please sign in to comment.