feat: Refactor session management and improve user session data retrieval

This commit is contained in:
Achintha Isuru
2026-02-17 16:05:45 -05:00
parent 631af65a2f
commit ddf270074b
8 changed files with 281 additions and 223 deletions

View File

@@ -24,9 +24,8 @@ import '../../domain/repositories/auth_repository_interface.dart';
/// identity management and Krow's Data Connect SDK for storing user profile data.
class AuthRepositoryImpl implements AuthRepositoryInterface {
/// Creates an [AuthRepositoryImpl] with the real dependencies.
AuthRepositoryImpl({
dc.DataConnectService? service,
}) : _service = service ?? dc.DataConnectService.instance;
AuthRepositoryImpl({dc.DataConnectService? service})
: _service = service ?? dc.DataConnectService.instance;
final dc.DataConnectService _service;
@@ -36,11 +35,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
required String password,
}) async {
try {
final firebase.UserCredential credential =
await _service.auth.signInWithEmailAndPassword(
email: email,
password: password,
);
final firebase.UserCredential credential = await _service.auth
.signInWithEmailAndPassword(email: email, password: password);
final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) {
@@ -60,9 +56,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
technicalMessage: 'Firebase error code: ${e.code}',
);
} else if (e.code == 'network-request-failed') {
throw NetworkException(
technicalMessage: 'Firebase: ${e.message}',
);
throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
} else {
throw SignInFailedException(
technicalMessage: 'Firebase auth error: ${e.message}',
@@ -71,9 +65,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
} on domain.AppException {
rethrow;
} catch (e) {
throw SignInFailedException(
technicalMessage: 'Unexpected error: $e',
);
throw SignInFailedException(technicalMessage: 'Unexpected error: $e');
}
}
@@ -88,11 +80,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
try {
// Step 1: Try to create Firebase Auth user
final firebase.UserCredential credential =
await _service.auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
final firebase.UserCredential credential = await _service.auth
.createUserWithEmailAndPassword(email: email, password: password);
firebaseUser = credential.user;
if (firebaseUser == null) {
@@ -111,9 +100,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
);
} on firebase.FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
throw WeakPasswordException(
technicalMessage: 'Firebase: ${e.message}',
);
throw WeakPasswordException(technicalMessage: 'Firebase: ${e.message}');
} else if (e.code == 'email-already-in-use') {
// Email exists in Firebase Auth - try to sign in and complete registration
return await _handleExistingFirebaseAccount(
@@ -122,9 +109,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
companyName: companyName,
);
} else if (e.code == 'network-request-failed') {
throw NetworkException(
technicalMessage: 'Firebase: ${e.message}',
);
throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
} else {
throw SignUpFailedException(
technicalMessage: 'Firebase auth error: ${e.message}',
@@ -133,15 +118,17 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
} on domain.AppException {
// Rollback for our known exceptions
await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId);
firebaseUser: firebaseUser,
businessId: createdBusinessId,
);
rethrow;
} catch (e) {
// Rollback: Clean up any partially created resources
await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId);
throw SignUpFailedException(
technicalMessage: 'Unexpected error: $e',
firebaseUser: firebaseUser,
businessId: createdBusinessId,
);
throw SignUpFailedException(technicalMessage: 'Unexpected error: $e');
}
}
@@ -161,16 +148,15 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
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 _service.auth.signInWithEmailAndPassword(
email: email,
password: password,
);
final firebase.UserCredential credential = await _service.auth
.signInWithEmailAndPassword(email: email, password: password);
final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) {
@@ -180,32 +166,40 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
}
// 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',
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');
'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
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
@@ -229,8 +223,10 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// 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',
@@ -242,7 +238,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
Future<bool> _checkBusinessUserExists(String firebaseUserId) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await _service.run(
() => _service.connector.getUserById(id: firebaseUserId).execute());
() => _service.connector.getUserById(id: firebaseUserId).execute(),
);
final dc.GetUserByIdUser? user = response.data.user;
return user != null &&
(user.userRole == 'BUSINESS' || user.userRole == 'BOTH');
@@ -258,14 +255,16 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Create Business entity in PostgreSQL
final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables>
createBusinessResponse = await _service.run(() => _service.connector
.createBusiness(
businessName: companyName,
userId: firebaseUser.uid,
rateGroup: dc.BusinessRateGroup.STANDARD,
status: dc.BusinessStatus.PENDING,
)
.execute());
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;
@@ -273,28 +272,28 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Check if User entity already exists in PostgreSQL
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> userResult =
await _service.run(() =>
_service.connector.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 _service.run(() => _service.connector
.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 _service.run(() => _service.connector
.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(
@@ -340,7 +339,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
@override
Future<domain.User> signInWithSocial({required String provider}) {
throw UnimplementedError(
'Social authentication with $provider is not yet implemented.');
'Social authentication with $provider is not yet implemented.',
);
}
Future<domain.User> _getUserProfile({
@@ -349,8 +349,9 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
bool requireBusinessRole = false,
}) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await _service.run(() =>
_service.connector.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(
@@ -383,22 +384,22 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
role: user.role.stringValue,
);
final QueryResult<dc.GetBusinessesByUserIdData,
dc.GetBusinessesByUserIdVariables> businessResponse =
await _service.run(() => _service.connector
.getBusinessesByUserId(
userId: firebaseUserId,
)
.execute());
final QueryResult<
dc.GetBusinessesByUserIdData,
dc.GetBusinessesByUserIdVariables
>
businessResponse = await _service.run(
() => _service.connector
.getBusinessesByUserId(userId: firebaseUserId)
.execute(),
);
final dc.GetBusinessesByUserIdBusinesses? business =
businessResponse.data.businesses.isNotEmpty
? businessResponse.data.businesses.first
: null;
? businessResponse.data.businesses.first
: null;
dc.ClientSessionStore.instance.setSession(
dc.ClientSession(
user: domainUser,
userPhotoUrl: user.photoUrl,
business: business == null
? null
: dc.ClientBusinessSession(