|
|
|
|
@@ -1,43 +1,156 @@
|
|
|
|
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
|
|
|
|
import 'package:krow_domain/krow_domain.dart';
|
|
|
|
|
|
|
|
|
|
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
|
|
|
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
|
|
|
|
import 'package:krow_domain/krow_domain.dart' as domain;
|
|
|
|
|
import '../../domain/repositories/auth_repository_interface.dart';
|
|
|
|
|
|
|
|
|
|
/// Production-ready implementation of the [AuthRepositoryInterface].
|
|
|
|
|
/// Production-ready implementation of the [AuthRepositoryInterface] for the client app.
|
|
|
|
|
///
|
|
|
|
|
/// This implementation integrates with the [krow_data_connect] package to provide
|
|
|
|
|
/// authentication services. It delegates actual data operations to the
|
|
|
|
|
/// [AuthRepositoryMock] (or eventually the real implementation) injected from the app layer.
|
|
|
|
|
/// This implementation integrates with Firebase Authentication for user
|
|
|
|
|
/// identity management and Krow's Data Connect SDK for storing user profile data.
|
|
|
|
|
class AuthRepositoryImpl implements AuthRepositoryInterface {
|
|
|
|
|
/// The data source used for authentication operations.
|
|
|
|
|
final AuthRepositoryMock _dataSource;
|
|
|
|
|
final firebase.FirebaseAuth _firebaseAuth;
|
|
|
|
|
final dc.ExampleConnector _dataConnect;
|
|
|
|
|
|
|
|
|
|
/// Creates an [AuthRepositoryImpl] with the injected [dataSource] dependency.
|
|
|
|
|
AuthRepositoryImpl({required AuthRepositoryMock dataSource})
|
|
|
|
|
: _dataSource = dataSource;
|
|
|
|
|
/// Creates an [AuthRepositoryImpl] with the real dependencies.
|
|
|
|
|
AuthRepositoryImpl({
|
|
|
|
|
required firebase.FirebaseAuth firebaseAuth,
|
|
|
|
|
required dc.ExampleConnector dataConnect,
|
|
|
|
|
}) : _firebaseAuth = firebaseAuth,
|
|
|
|
|
_dataConnect = dataConnect;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<User> signInWithEmail({
|
|
|
|
|
Future<domain.User> signInWithEmail({
|
|
|
|
|
required String email,
|
|
|
|
|
required String password,
|
|
|
|
|
}) {
|
|
|
|
|
return _dataSource.signInWithEmail(email, password);
|
|
|
|
|
}) async {
|
|
|
|
|
try {
|
|
|
|
|
final credential = await _firebaseAuth.signInWithEmailAndPassword(
|
|
|
|
|
email: email,
|
|
|
|
|
password: password,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final firebaseUser = credential.user;
|
|
|
|
|
if (firebaseUser == null) {
|
|
|
|
|
throw Exception('Sign-in failed, no Firebase user received.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _getUserProfile(
|
|
|
|
|
firebaseUserId: firebaseUser.uid,
|
|
|
|
|
fallbackEmail: firebaseUser.email ?? email,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
//TO-DO: validate that user is business role and has business account
|
|
|
|
|
|
|
|
|
|
} on firebase.FirebaseAuthException catch (e) {
|
|
|
|
|
if (e.code == 'invalid-credential' || e.code == 'wrong-password') {
|
|
|
|
|
throw Exception('Incorrect email or password.');
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Authentication error: ${e.message}');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw Exception('Failed to sign in and fetch user data: ${e.toString()}');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<User> signUpWithEmail({
|
|
|
|
|
Future<domain.User> signUpWithEmail({
|
|
|
|
|
required String companyName,
|
|
|
|
|
required String email,
|
|
|
|
|
required String password,
|
|
|
|
|
}) {
|
|
|
|
|
return _dataSource.signUpWithEmail(email, password, companyName);
|
|
|
|
|
}) async {
|
|
|
|
|
try {
|
|
|
|
|
final credential = await _firebaseAuth.createUserWithEmailAndPassword(
|
|
|
|
|
email: email,
|
|
|
|
|
password: password,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
final firebaseUser = credential.user;
|
|
|
|
|
if (firebaseUser == null) {
|
|
|
|
|
throw Exception('Sign-up failed, Firebase user could not be created.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client-specific business logic:
|
|
|
|
|
// 1. Create a `Business` entity.
|
|
|
|
|
// 2. Create a `User` entity associated with the business.
|
|
|
|
|
final createBusinessResponse = await _dataConnect.createBusiness(
|
|
|
|
|
businessName: companyName,
|
|
|
|
|
userId: firebaseUser.uid,
|
|
|
|
|
rateGroup: dc.BusinessRateGroup.STANDARD,
|
|
|
|
|
status: dc.BusinessStatus.PENDING,
|
|
|
|
|
).execute();
|
|
|
|
|
|
|
|
|
|
final businessData = createBusinessResponse.data?.business_insert;
|
|
|
|
|
if (businessData == null) {
|
|
|
|
|
await firebaseUser.delete(); // Rollback if business creation fails
|
|
|
|
|
throw Exception('Business creation failed after Firebase user registration.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final createUserResponse = await _dataConnect.createUser(
|
|
|
|
|
id: firebaseUser.uid,
|
|
|
|
|
role: dc.UserBaseRole.USER,
|
|
|
|
|
)
|
|
|
|
|
.email(email)
|
|
|
|
|
.userRole('BUSINESS')
|
|
|
|
|
.execute();
|
|
|
|
|
|
|
|
|
|
final newUserData = createUserResponse.data?.user_insert;
|
|
|
|
|
if (newUserData == null) {
|
|
|
|
|
await firebaseUser.delete(); // Rollback if user profile creation fails
|
|
|
|
|
// TO-DO: Also delete the created Business if this fails
|
|
|
|
|
throw Exception('User profile creation failed after Firebase user registration.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _getUserProfile(
|
|
|
|
|
firebaseUserId: firebaseUser.uid,
|
|
|
|
|
fallbackEmail: firebaseUser.email ?? email,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
} on firebase.FirebaseAuthException catch (e) {
|
|
|
|
|
if (e.code == 'weak-password') {
|
|
|
|
|
throw Exception('The password provided is too weak.');
|
|
|
|
|
} else if (e.code == 'email-already-in-use') {
|
|
|
|
|
throw Exception('An account already exists for that email address.');
|
|
|
|
|
} else {
|
|
|
|
|
throw Exception('Sign-up error: ${e.message}');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw Exception('Failed to sign up and create user data: ${e.toString()}');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<User> signInWithSocial({required String provider}) {
|
|
|
|
|
return _dataSource.signInWithSocial(provider);
|
|
|
|
|
Future<void> signOut() async {
|
|
|
|
|
try {
|
|
|
|
|
await _firebaseAuth.signOut();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw Exception('Error signing out: ${e.toString()}');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<void> signOut() => _dataSource.signOut();
|
|
|
|
|
Future<domain.User> signInWithSocial({required String provider}) {
|
|
|
|
|
throw UnimplementedError('Social authentication with $provider is not yet implemented.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<domain.User> _getUserProfile({
|
|
|
|
|
required String firebaseUserId,
|
|
|
|
|
required String? fallbackEmail,
|
|
|
|
|
}) async {
|
|
|
|
|
final response = await _dataConnect.getUserById(id: firebaseUserId).execute();
|
|
|
|
|
final user = response.data?.user;
|
|
|
|
|
if (user == null) {
|
|
|
|
|
throw Exception('Authenticated user profile not found in database.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final email = user.email ?? fallbackEmail;
|
|
|
|
|
if (email == null || email.isEmpty) {
|
|
|
|
|
throw Exception('User email is missing in profile data.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return domain.User(
|
|
|
|
|
id: user.id,
|
|
|
|
|
email: email,
|
|
|
|
|
role: user.role.stringValue,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|