diff --git a/apps/mobile/packages/features/client/authentication/lib/client_authentication.dart b/apps/mobile/packages/features/client/authentication/lib/client_authentication.dart index 12310fde..1ee73543 100644 --- a/apps/mobile/packages/features/client/authentication/lib/client_authentication.dart +++ b/apps/mobile/packages/features/client/authentication/lib/client_authentication.dart @@ -1,6 +1,5 @@ library; -import 'package:firebase_auth/firebase_auth.dart' as firebase; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; @@ -28,12 +27,7 @@ class ClientAuthenticationModule extends Module { @override void binds(Injector i) { // Repositories - i.addLazySingleton( - () => AuthRepositoryImpl( - firebaseAuth: firebase.FirebaseAuth.instance, - dataConnect: ExampleConnector.instance, - ), - ); + i.addLazySingleton(AuthRepositoryImpl.new); // UseCases i.addLazySingleton( diff --git a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index f9ea9264..467a7c07 100644 --- a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -12,7 +12,6 @@ import 'package:krow_domain/krow_domain.dart' AccountExistsException, UserNotFoundException, UnauthorizedAppException, - UnauthorizedAppException, PasswordMismatchException, NetworkException; import 'package:krow_domain/krow_domain.dart' as domain; @@ -23,18 +22,13 @@ import '../../domain/repositories/auth_repository_interface.dart'; /// /// This implementation integrates with Firebase Authentication for user /// identity management and Krow's Data Connect SDK for storing user profile data. -class AuthRepositoryImpl - with dc.DataErrorHandler - implements AuthRepositoryInterface { - +class AuthRepositoryImpl implements AuthRepositoryInterface { /// Creates an [AuthRepositoryImpl] with the real dependencies. AuthRepositoryImpl({ - required firebase.FirebaseAuth firebaseAuth, - required dc.ExampleConnector dataConnect, - }) : _firebaseAuth = firebaseAuth, - _dataConnect = dataConnect; - final firebase.FirebaseAuth _firebaseAuth; - final dc.ExampleConnector _dataConnect; + dc.DataConnectService? service, + }) : _service = service ?? dc.DataConnectService.instance; + + final dc.DataConnectService _service; @override Future signInWithEmail({ @@ -42,7 +36,8 @@ class AuthRepositoryImpl required String password, }) async { try { - final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword( + final firebase.UserCredential credential = + await _service.auth.signInWithEmailAndPassword( email: email, password: password, ); @@ -59,7 +54,6 @@ class AuthRepositoryImpl fallbackEmail: firebaseUser.email ?? email, requireBusinessRole: true, ); - } on firebase.FirebaseAuthException catch (e) { if (e.code == 'invalid-credential' || e.code == 'wrong-password') { throw InvalidCredentialsException( @@ -94,7 +88,8 @@ class AuthRepositoryImpl try { // Step 1: Try to create Firebase Auth user - final firebase.UserCredential credential = await _firebaseAuth.createUserWithEmailAndPassword( + final firebase.UserCredential credential = + await _service.auth.createUserWithEmailAndPassword( email: email, password: password, ); @@ -111,9 +106,9 @@ class AuthRepositoryImpl firebaseUser: firebaseUser, companyName: companyName, email: email, - onBusinessCreated: (String businessId) => createdBusinessId = businessId, + onBusinessCreated: (String businessId) => + createdBusinessId = businessId, ); - } on firebase.FirebaseAuthException catch (e) { if (e.code == 'weak-password') { throw WeakPasswordException( @@ -137,11 +132,13 @@ class AuthRepositoryImpl } } on domain.AppException { // Rollback for our known exceptions - await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId); + await _rollbackSignUp( + firebaseUser: firebaseUser, businessId: createdBusinessId); rethrow; } catch (e) { // Rollback: Clean up any partially created resources - await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId); + await _rollbackSignUp( + firebaseUser: firebaseUser, businessId: createdBusinessId); throw SignUpFailedException( technicalMessage: 'Unexpected error: $e', ); @@ -164,11 +161,13 @@ class AuthRepositoryImpl required String password, required String companyName, }) async { - developer.log('Email exists in Firebase, attempting sign-in: $email', name: 'AuthRepository'); + developer.log('Email exists in Firebase, attempting sign-in: $email', + name: 'AuthRepository'); try { // Try to sign in with the provided password - final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword( + final firebase.UserCredential credential = + await _service.auth.signInWithEmailAndPassword( email: email, password: password, ); @@ -181,28 +180,32 @@ class AuthRepositoryImpl } // Sign-in succeeded! Check if user already has a BUSINESS account in PostgreSQL - final bool hasBusinessAccount = await _checkBusinessUserExists(firebaseUser.uid); + final bool hasBusinessAccount = + await _checkBusinessUserExists(firebaseUser.uid); if (hasBusinessAccount) { // User already has a KROW Client account - developer.log('User already has BUSINESS account: ${firebaseUser.uid}', name: 'AuthRepository'); + developer.log('User already has BUSINESS account: ${firebaseUser.uid}', + name: 'AuthRepository'); throw AccountExistsException( technicalMessage: 'User ${firebaseUser.uid} already has BUSINESS role', ); } // User exists in Firebase but not in KROW PostgreSQL - create the entities - developer.log('Creating BUSINESS account for existing Firebase user: ${firebaseUser.uid}', name: 'AuthRepository'); + developer.log( + 'Creating BUSINESS account for existing Firebase user: ${firebaseUser.uid}', + name: 'AuthRepository'); return await _createBusinessAndUser( firebaseUser: firebaseUser, companyName: companyName, email: email, onBusinessCreated: (_) {}, // No rollback needed for existing Firebase user ); - } on firebase.FirebaseAuthException catch (e) { // Sign-in failed - check why - developer.log('Sign-in failed with code: ${e.code}', name: 'AuthRepository'); + developer.log('Sign-in failed with code: ${e.code}', + name: 'AuthRepository'); if (e.code == 'wrong-password' || e.code == 'invalid-credential') { // Password doesn't match - check what providers are available @@ -226,9 +229,11 @@ class AuthRepositoryImpl // We can't distinguish between "wrong password" and "no password provider" // due to Firebase deprecating fetchSignInMethodsForEmail. // The PasswordMismatchException message covers both scenarios. - developer.log('Password mismatch or different provider for: $email', name: 'AuthRepository'); + developer.log('Password mismatch or different provider for: $email', + name: 'AuthRepository'); throw PasswordMismatchException( - technicalMessage: 'Email $email: password mismatch or different auth provider', + technicalMessage: + 'Email $email: password mismatch or different auth provider', ); } @@ -236,9 +241,11 @@ class AuthRepositoryImpl Future _checkBusinessUserExists(String firebaseUserId) async { final QueryResult response = - await executeProtected(() => _dataConnect.getUserById(id: firebaseUserId).execute()); + await _service.run( + () => _service.connector.getUserById(id: firebaseUserId).execute()); final dc.GetUserByIdUser? user = response.data.user; - return user != null && (user.userRole == 'BUSINESS' || user.userRole == 'BOTH'); + return user != null && + (user.userRole == 'BUSINESS' || user.userRole == 'BOTH'); } /// Creates Business and User entities in PostgreSQL for a Firebase user. @@ -250,38 +257,44 @@ class AuthRepositoryImpl }) async { // Create Business entity in PostgreSQL - final OperationResult createBusinessResponse = - await executeProtected(() => _dataConnect.createBusiness( - businessName: companyName, - userId: firebaseUser.uid, - rateGroup: dc.BusinessRateGroup.STANDARD, - status: dc.BusinessStatus.PENDING, - ).execute()); + final OperationResult + createBusinessResponse = await _service.run(() => _service.connector + .createBusiness( + businessName: companyName, + userId: firebaseUser.uid, + rateGroup: dc.BusinessRateGroup.STANDARD, + status: dc.BusinessStatus.PENDING, + ) + .execute()); - final dc.CreateBusinessBusinessInsert businessData = createBusinessResponse.data.business_insert; + final dc.CreateBusinessBusinessInsert businessData = + createBusinessResponse.data.business_insert; onBusinessCreated(businessData.id); // Check if User entity already exists in PostgreSQL final QueryResult userResult = - await executeProtected(() => _dataConnect.getUserById(id: firebaseUser.uid).execute()); + await _service.run(() => + _service.connector.getUserById(id: firebaseUser.uid).execute()); final dc.GetUserByIdUser? existingUser = userResult.data.user; if (existingUser != null) { // User exists (likely in another app like STAFF). Update role to BOTH. - await executeProtected(() => _dataConnect.updateUser( - id: firebaseUser.uid, - ) - .userRole('BOTH') - .execute()); + await _service.run(() => _service.connector + .updateUser( + id: firebaseUser.uid, + ) + .userRole('BOTH') + .execute()); } else { // Create new User entity in PostgreSQL - await executeProtected(() => _dataConnect.createUser( - id: firebaseUser.uid, - role: dc.UserBaseRole.USER, - ) - .email(email) - .userRole('BUSINESS') - .execute()); + await _service.run(() => _service.connector + .createUser( + id: firebaseUser.uid, + role: dc.UserBaseRole.USER, + ) + .email(email) + .userRole('BUSINESS') + .execute()); } return _getUserProfile( @@ -298,7 +311,7 @@ class AuthRepositoryImpl // Delete business first (if created) if (businessId != null) { try { - await _dataConnect.deleteBusiness(id: businessId).execute(); + await _service.connector.deleteBusiness(id: businessId).execute(); } catch (_) { // Log but don't throw - we're already in error recovery } @@ -316,8 +329,9 @@ class AuthRepositoryImpl @override Future signOut() async { try { - await _firebaseAuth.signOut(); + await _service.auth.signOut(); dc.ClientSessionStore.instance.clear(); + _service.clearCache(); } catch (e) { throw Exception('Error signing out: ${e.toString()}'); } @@ -325,7 +339,8 @@ class AuthRepositoryImpl @override Future signInWithSocial({required String provider}) { - throw UnimplementedError('Social authentication with $provider is not yet implemented.'); + throw UnimplementedError( + 'Social authentication with $provider is not yet implemented.'); } Future _getUserProfile({ @@ -334,18 +349,24 @@ class AuthRepositoryImpl bool requireBusinessRole = false, }) async { final QueryResult response = - await executeProtected(() => _dataConnect.getUserById(id: firebaseUserId).execute()); + await _service.run(() => + _service.connector.getUserById(id: firebaseUserId).execute()); final dc.GetUserByIdUser? user = response.data.user; if (user == null) { throw UserNotFoundException( - technicalMessage: 'Firebase UID $firebaseUserId not found in users table', + technicalMessage: + 'Firebase UID $firebaseUserId not found in users table', ); } - if (requireBusinessRole && user.userRole != 'BUSINESS' && user.userRole != 'BOTH') { - await _firebaseAuth.signOut(); + if (requireBusinessRole && + user.userRole != 'BUSINESS' && + user.userRole != 'BOTH') { + await _service.auth.signOut(); dc.ClientSessionStore.instance.clear(); + _service.clearCache(); throw UnauthorizedAppException( - technicalMessage: 'User role is ${user.userRole}, expected BUSINESS or BOTH', + technicalMessage: + 'User role is ${user.userRole}, expected BUSINESS or BOTH', ); } @@ -362,13 +383,17 @@ class AuthRepositoryImpl role: user.role.stringValue, ); - final QueryResult businessResponse = - await executeProtected(() => _dataConnect.getBusinessesByUserId( - userId: firebaseUserId, - ).execute()); - final dc.GetBusinessesByUserIdBusinesses? business = businessResponse.data.businesses.isNotEmpty - ? businessResponse.data.businesses.first - : null; + final QueryResult businessResponse = + await _service.run(() => _service.connector + .getBusinessesByUserId( + userId: firebaseUserId, + ) + .execute()); + final dc.GetBusinessesByUserIdBusinesses? business = + businessResponse.data.businesses.isNotEmpty + ? businessResponse.data.businesses.first + : null; dc.ClientSessionStore.instance.setSession( dc.ClientSession(