feat(auth-repository): Refactor AuthRepositoryImpl to remove FirebaseAuth dependency and utilize DataConnectService

This commit is contained in:
Achintha Isuru
2026-02-16 17:21:03 -05:00
parent fdd40ba72c
commit fc0bb5828c
2 changed files with 91 additions and 72 deletions

View File

@@ -1,6 +1,5 @@
library; library;
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
@@ -28,12 +27,7 @@ class ClientAuthenticationModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addLazySingleton<AuthRepositoryInterface>( i.addLazySingleton<AuthRepositoryInterface>(AuthRepositoryImpl.new);
() => AuthRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
// UseCases // UseCases
i.addLazySingleton( i.addLazySingleton(

View File

@@ -12,7 +12,6 @@ import 'package:krow_domain/krow_domain.dart'
AccountExistsException, AccountExistsException,
UserNotFoundException, UserNotFoundException,
UnauthorizedAppException, UnauthorizedAppException,
UnauthorizedAppException,
PasswordMismatchException, PasswordMismatchException,
NetworkException; NetworkException;
import 'package:krow_domain/krow_domain.dart' as domain; 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 /// This implementation integrates with Firebase Authentication for user
/// identity management and Krow's Data Connect SDK for storing user profile data. /// identity management and Krow's Data Connect SDK for storing user profile data.
class AuthRepositoryImpl class AuthRepositoryImpl implements AuthRepositoryInterface {
with dc.DataErrorHandler
implements AuthRepositoryInterface {
/// Creates an [AuthRepositoryImpl] with the real dependencies. /// Creates an [AuthRepositoryImpl] with the real dependencies.
AuthRepositoryImpl({ AuthRepositoryImpl({
required firebase.FirebaseAuth firebaseAuth, dc.DataConnectService? service,
required dc.ExampleConnector dataConnect, }) : _service = service ?? dc.DataConnectService.instance;
}) : _firebaseAuth = firebaseAuth,
_dataConnect = dataConnect; final dc.DataConnectService _service;
final firebase.FirebaseAuth _firebaseAuth;
final dc.ExampleConnector _dataConnect;
@override @override
Future<domain.User> signInWithEmail({ Future<domain.User> signInWithEmail({
@@ -42,7 +36,8 @@ class AuthRepositoryImpl
required String password, required String password,
}) async { }) async {
try { try {
final firebase.UserCredential credential = await _firebaseAuth.signInWithEmailAndPassword( final firebase.UserCredential credential =
await _service.auth.signInWithEmailAndPassword(
email: email, email: email,
password: password, password: password,
); );
@@ -59,7 +54,6 @@ class AuthRepositoryImpl
fallbackEmail: firebaseUser.email ?? email, fallbackEmail: firebaseUser.email ?? email,
requireBusinessRole: true, requireBusinessRole: true,
); );
} on firebase.FirebaseAuthException catch (e) { } on firebase.FirebaseAuthException catch (e) {
if (e.code == 'invalid-credential' || e.code == 'wrong-password') { if (e.code == 'invalid-credential' || e.code == 'wrong-password') {
throw InvalidCredentialsException( throw InvalidCredentialsException(
@@ -94,7 +88,8 @@ class AuthRepositoryImpl
try { try {
// Step 1: Try to create Firebase Auth user // 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, email: email,
password: password, password: password,
); );
@@ -111,9 +106,9 @@ class AuthRepositoryImpl
firebaseUser: firebaseUser, firebaseUser: firebaseUser,
companyName: companyName, companyName: companyName,
email: email, email: email,
onBusinessCreated: (String businessId) => createdBusinessId = businessId, onBusinessCreated: (String businessId) =>
createdBusinessId = businessId,
); );
} on firebase.FirebaseAuthException catch (e) { } on firebase.FirebaseAuthException catch (e) {
if (e.code == 'weak-password') { if (e.code == 'weak-password') {
throw WeakPasswordException( throw WeakPasswordException(
@@ -137,11 +132,13 @@ class AuthRepositoryImpl
} }
} on domain.AppException { } on domain.AppException {
// Rollback for our known exceptions // Rollback for our known exceptions
await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId); await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId);
rethrow; rethrow;
} catch (e) { } catch (e) {
// Rollback: Clean up any partially created resources // Rollback: Clean up any partially created resources
await _rollbackSignUp(firebaseUser: firebaseUser, businessId: createdBusinessId); await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId);
throw SignUpFailedException( throw SignUpFailedException(
technicalMessage: 'Unexpected error: $e', technicalMessage: 'Unexpected error: $e',
); );
@@ -164,11 +161,13 @@ class AuthRepositoryImpl
required String password, required String password,
required String companyName, required String companyName,
}) async { }) 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 {
// Try to sign in with the provided password // 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, email: email,
password: password, password: password,
); );
@@ -181,28 +180,32 @@ class AuthRepositoryImpl
} }
// Sign-in succeeded! Check if user already has a BUSINESS account in PostgreSQL // 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) { if (hasBusinessAccount) {
// User already has a KROW Client account // 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( throw AccountExistsException(
technicalMessage: 'User ${firebaseUser.uid} already has BUSINESS role', technicalMessage: 'User ${firebaseUser.uid} already has BUSINESS role',
); );
} }
// User exists in Firebase but not in KROW PostgreSQL - create the entities // 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( return await _createBusinessAndUser(
firebaseUser: firebaseUser, firebaseUser: firebaseUser,
companyName: companyName, companyName: companyName,
email: email, email: email,
onBusinessCreated: (_) {}, // No rollback needed for existing Firebase user onBusinessCreated: (_) {}, // No rollback needed for existing Firebase user
); );
} on firebase.FirebaseAuthException catch (e) { } on firebase.FirebaseAuthException catch (e) {
// Sign-in failed - check why // 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') { if (e.code == 'wrong-password' || e.code == 'invalid-credential') {
// Password doesn't match - check what providers are available // 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" // We can't distinguish between "wrong password" and "no password provider"
// due to Firebase deprecating fetchSignInMethodsForEmail. // due to Firebase deprecating fetchSignInMethodsForEmail.
// The PasswordMismatchException message covers both scenarios. // 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( 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<bool> _checkBusinessUserExists(String firebaseUserId) async { Future<bool> _checkBusinessUserExists(String firebaseUserId) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await executeProtected(() => _dataConnect.getUserById(id: firebaseUserId).execute()); await _service.run(
() => _service.connector.getUserById(id: firebaseUserId).execute());
final dc.GetUserByIdUser? user = response.data.user; 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. /// Creates Business and User entities in PostgreSQL for a Firebase user.
@@ -250,38 +257,44 @@ class AuthRepositoryImpl
}) async { }) async {
// Create Business entity in PostgreSQL // Create Business entity in PostgreSQL
final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables> createBusinessResponse = final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables>
await executeProtected(() => _dataConnect.createBusiness( createBusinessResponse = await _service.run(() => _service.connector
businessName: companyName, .createBusiness(
userId: firebaseUser.uid, businessName: companyName,
rateGroup: dc.BusinessRateGroup.STANDARD, userId: firebaseUser.uid,
status: dc.BusinessStatus.PENDING, rateGroup: dc.BusinessRateGroup.STANDARD,
).execute()); status: dc.BusinessStatus.PENDING,
)
.execute());
final dc.CreateBusinessBusinessInsert businessData = createBusinessResponse.data.business_insert; final dc.CreateBusinessBusinessInsert businessData =
createBusinessResponse.data.business_insert;
onBusinessCreated(businessData.id); onBusinessCreated(businessData.id);
// Check if User entity already exists in PostgreSQL // Check if User entity already exists in PostgreSQL
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> userResult = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> 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; final dc.GetUserByIdUser? existingUser = userResult.data.user;
if (existingUser != null) { if (existingUser != null) {
// User exists (likely in another app like STAFF). Update role to BOTH. // User exists (likely in another app like STAFF). Update role to BOTH.
await executeProtected(() => _dataConnect.updateUser( await _service.run(() => _service.connector
id: firebaseUser.uid, .updateUser(
) id: firebaseUser.uid,
.userRole('BOTH') )
.execute()); .userRole('BOTH')
.execute());
} else { } else {
// Create new User entity in PostgreSQL // Create new User entity in PostgreSQL
await executeProtected(() => _dataConnect.createUser( await _service.run(() => _service.connector
id: firebaseUser.uid, .createUser(
role: dc.UserBaseRole.USER, id: firebaseUser.uid,
) role: dc.UserBaseRole.USER,
.email(email) )
.userRole('BUSINESS') .email(email)
.execute()); .userRole('BUSINESS')
.execute());
} }
return _getUserProfile( return _getUserProfile(
@@ -298,7 +311,7 @@ class AuthRepositoryImpl
// Delete business first (if created) // Delete business first (if created)
if (businessId != null) { if (businessId != null) {
try { try {
await _dataConnect.deleteBusiness(id: businessId).execute(); await _service.connector.deleteBusiness(id: businessId).execute();
} catch (_) { } catch (_) {
// Log but don't throw - we're already in error recovery // Log but don't throw - we're already in error recovery
} }
@@ -316,8 +329,9 @@ class AuthRepositoryImpl
@override @override
Future<void> signOut() async { Future<void> signOut() async {
try { try {
await _firebaseAuth.signOut(); await _service.auth.signOut();
dc.ClientSessionStore.instance.clear(); dc.ClientSessionStore.instance.clear();
_service.clearCache();
} catch (e) { } catch (e) {
throw Exception('Error signing out: ${e.toString()}'); throw Exception('Error signing out: ${e.toString()}');
} }
@@ -325,7 +339,8 @@ class AuthRepositoryImpl
@override @override
Future<domain.User> signInWithSocial({required String provider}) { Future<domain.User> 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<domain.User> _getUserProfile({ Future<domain.User> _getUserProfile({
@@ -334,18 +349,24 @@ class AuthRepositoryImpl
bool requireBusinessRole = false, bool requireBusinessRole = false,
}) async { }) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await executeProtected(() => _dataConnect.getUserById(id: firebaseUserId).execute()); await _service.run(() =>
_service.connector.getUserById(id: firebaseUserId).execute());
final dc.GetUserByIdUser? user = response.data.user; final dc.GetUserByIdUser? user = response.data.user;
if (user == null) { if (user == null) {
throw UserNotFoundException( 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') { if (requireBusinessRole &&
await _firebaseAuth.signOut(); user.userRole != 'BUSINESS' &&
user.userRole != 'BOTH') {
await _service.auth.signOut();
dc.ClientSessionStore.instance.clear(); dc.ClientSessionStore.instance.clear();
_service.clearCache();
throw UnauthorizedAppException( 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, role: user.role.stringValue,
); );
final QueryResult<dc.GetBusinessesByUserIdData, dc.GetBusinessesByUserIdVariables> businessResponse = final QueryResult<dc.GetBusinessesByUserIdData,
await executeProtected(() => _dataConnect.getBusinessesByUserId( dc.GetBusinessesByUserIdVariables> businessResponse =
userId: firebaseUserId, await _service.run(() => _service.connector
).execute()); .getBusinessesByUserId(
final dc.GetBusinessesByUserIdBusinesses? business = businessResponse.data.businesses.isNotEmpty userId: firebaseUserId,
? businessResponse.data.businesses.first )
: null; .execute());
final dc.GetBusinessesByUserIdBusinesses? business =
businessResponse.data.businesses.isNotEmpty
? businessResponse.data.businesses.first
: null;
dc.ClientSessionStore.instance.setSession( dc.ClientSessionStore.instance.setSession(
dc.ClientSession( dc.ClientSession(