327 lines
10 KiB
Dart
327 lines
10 KiB
Dart
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import '../models/user.dart';
|
|
import '../services/auth_service.dart';
|
|
import '../services/error_service.dart';
|
|
import '../services/notification_service.dart';
|
|
|
|
/// Repository for authentication operations and user data management.
|
|
///
|
|
/// This repository handles authentication logic and user data persistence
|
|
/// by coordinating between Firebase Auth and Firestore. It provides a
|
|
/// clean interface for authentication operations while managing the
|
|
/// relationship between Firebase users and application user data.
|
|
class AuthRepository {
|
|
/// Authentication service for Firebase Auth operations.
|
|
final AuthService _authService;
|
|
|
|
/// Firestore instance for user data operations.
|
|
final FirebaseFirestore _firestore;
|
|
|
|
/// Error service for logging authentication errors.
|
|
final _errorService = ErrorService();
|
|
|
|
/// Creates a new [AuthRepository] with optional service dependencies.
|
|
///
|
|
/// If [authService] or [firestore] are not provided, default instances will be used.
|
|
AuthRepository({AuthService? authService, FirebaseFirestore? firestore})
|
|
: _authService = authService ?? AuthService(),
|
|
_firestore = firestore ?? FirebaseFirestore.instance;
|
|
|
|
/// Stream of authentication state changes.
|
|
///
|
|
/// Emits Firebase user objects when authentication state changes.
|
|
Stream<firebase_auth.User?> get authStateChanges =>
|
|
_authService.authStateChanges;
|
|
|
|
/// Gets the currently authenticated Firebase user.
|
|
///
|
|
/// Returns null if no user is currently authenticated.
|
|
firebase_auth.User? get currentUser => _authService.currentUser;
|
|
|
|
/// Signs in a user with email and password.
|
|
///
|
|
/// Authenticates with Firebase Auth and retrieves the corresponding
|
|
/// user data from Firestore.
|
|
///
|
|
/// [email] - User's email address
|
|
/// [password] - User's password
|
|
///
|
|
/// Returns the [User] model if successful, null if user data not found.
|
|
/// Throws an exception if authentication fails.
|
|
Future<User?> signInWithEmailAndPassword({
|
|
required String email,
|
|
required String password,
|
|
}) async {
|
|
try {
|
|
final firebaseUser = await _authService.signInWithEmailAndPassword(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
await _saveFCMToken(firebaseUser.user!.uid);
|
|
return await getUserFromFirestore(firebaseUser.user!.uid);
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError('AuthRepository', 'SignIn error: $e', stackTrace);
|
|
throw Exception('Utilisateur ou mot de passe incorrect');
|
|
}
|
|
}
|
|
|
|
/// Creates a new user account with email and password.
|
|
///
|
|
/// Creates a Firebase Auth account and stores additional user information
|
|
/// in Firestore.
|
|
///
|
|
/// [email] - User's email address
|
|
/// [password] - User's password
|
|
/// [nom] - User's last name
|
|
/// [prenom] - User's first name
|
|
/// [phoneNumber] - User's phone number
|
|
///
|
|
/// Returns the created [User] model if successful.
|
|
/// Throws an exception if account creation fails.
|
|
Future<User?> signUpWithEmailAndPassword({
|
|
required String email,
|
|
required String password,
|
|
required String nom,
|
|
required String prenom,
|
|
required String phoneNumber,
|
|
}) async {
|
|
try {
|
|
final firebaseUser = await _authService.signUpWithEmailAndPassword(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
|
|
// Create user document in Firestore with additional information
|
|
final user = User(
|
|
id: firebaseUser.user!.uid,
|
|
email: email,
|
|
nom: nom,
|
|
prenom: prenom,
|
|
phoneNumber: phoneNumber,
|
|
platform: 'email',
|
|
profilePictureUrl: '',
|
|
);
|
|
|
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
|
if (user.id != null) {
|
|
await _saveFCMToken(user.id!);
|
|
}
|
|
return user;
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError('AuthRepository', 'SignUp error: $e', stackTrace);
|
|
throw Exception('Erreur lors de la création du compte');
|
|
}
|
|
}
|
|
|
|
/// Signs in a user using Google authentication.
|
|
///
|
|
/// Handles Google sign-in flow and creates/retrieves user data from Firestore.
|
|
/// If the user doesn't exist, creates a new user document.
|
|
///
|
|
/// Returns the [User] model if successful, null if Google sign-in was cancelled.
|
|
/// Throws an exception if authentication fails.
|
|
Future<User?> signUpWithGoogle(
|
|
String phoneNumber,
|
|
String name,
|
|
String firstname,
|
|
) async {
|
|
try {
|
|
firebase_auth.User? firebaseUser = _authService.currentUser;
|
|
|
|
if (firebaseUser == null) {
|
|
final userCredential = await _authService.signInWithGoogle();
|
|
firebaseUser = userCredential.user;
|
|
}
|
|
|
|
if (firebaseUser != null) {
|
|
// Check if user already exists in Firestore
|
|
final existingUser = await getUserFromFirestore(firebaseUser.uid);
|
|
|
|
if (existingUser != null) {
|
|
return existingUser;
|
|
}
|
|
|
|
// Create new user document for first-time Google sign-in
|
|
final user = User(
|
|
id: firebaseUser.uid,
|
|
email: firebaseUser.email ?? '',
|
|
nom: name,
|
|
prenom: firstname,
|
|
phoneNumber: phoneNumber,
|
|
profilePictureUrl: firebaseUser.photoURL ?? 'Unknown',
|
|
platform: 'google',
|
|
);
|
|
|
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
|
if (user.id != null) {
|
|
await _saveFCMToken(user.id!);
|
|
}
|
|
return user;
|
|
}
|
|
return null;
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'AuthRepository',
|
|
'Google SignUp error: $e',
|
|
stackTrace,
|
|
);
|
|
throw Exception('Erreur lors de la connexion Google');
|
|
}
|
|
}
|
|
|
|
Future<User?> signInWithGoogle() async {
|
|
try {
|
|
final firebaseUser = await _authService.signInWithGoogle();
|
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
|
if (user != null) {
|
|
if (user.id != null) {
|
|
await _saveFCMToken(user.id!);
|
|
}
|
|
return user;
|
|
} else {
|
|
throw Exception('Utilisateur non trouvé');
|
|
}
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'AuthRepository',
|
|
'Google SignIn error: $e',
|
|
stackTrace,
|
|
);
|
|
throw Exception('Erreur lors de la connexion Google');
|
|
}
|
|
}
|
|
|
|
/// Signs in a user using Apple authentication.
|
|
///
|
|
/// Handles Apple sign-in flow and creates/retrieves user data from Firestore.
|
|
/// If the user doesn't exist, creates a new user document.
|
|
///
|
|
/// Returns the [User] model if successful, null if Apple sign-in was cancelled.
|
|
/// Throws an exception if authentication fails.
|
|
Future<User?> signUpWithApple(
|
|
String phoneNumber,
|
|
String name,
|
|
String firstname,
|
|
) async {
|
|
try {
|
|
firebase_auth.User? firebaseUser = _authService.currentUser;
|
|
|
|
if (firebaseUser == null) {
|
|
final userCredential = await _authService.signInWithApple();
|
|
firebaseUser = userCredential.user;
|
|
}
|
|
|
|
if (firebaseUser != null) {
|
|
final existingUser = await getUserFromFirestore(firebaseUser.uid);
|
|
|
|
if (existingUser != null) {
|
|
return existingUser;
|
|
}
|
|
|
|
final user = User(
|
|
id: firebaseUser.uid,
|
|
email: firebaseUser.email ?? '',
|
|
nom: name,
|
|
prenom: firstname,
|
|
phoneNumber: phoneNumber,
|
|
profilePictureUrl: firebaseUser.photoURL ?? 'Unknown',
|
|
platform: 'apple',
|
|
);
|
|
|
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
|
if (user.id != null) {
|
|
await _saveFCMToken(user.id!);
|
|
}
|
|
return user;
|
|
}
|
|
return null;
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'AuthRepository',
|
|
'Apple SignUp error: $e',
|
|
stackTrace,
|
|
);
|
|
throw Exception('Erreur lors de la connexion Apple');
|
|
}
|
|
}
|
|
|
|
Future<User?> signInWithApple() async {
|
|
try {
|
|
final firebaseUser = await _authService.signInWithApple();
|
|
final user = await getUserFromFirestore(firebaseUser.user!.uid);
|
|
if (user != null) {
|
|
if (user.id != null) {
|
|
await _saveFCMToken(user.id!);
|
|
}
|
|
return user;
|
|
} else {
|
|
throw Exception('Utilisateur non trouvé');
|
|
}
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'AuthRepository',
|
|
'Apple SignIn error: $e',
|
|
stackTrace,
|
|
);
|
|
throw Exception('Erreur lors de la connexion Apple');
|
|
}
|
|
}
|
|
|
|
/// Signs out the current user.
|
|
///
|
|
/// Clears the authentication state and signs out from Firebase Auth.
|
|
Future<void> signOut() async {
|
|
await _authService.signOut();
|
|
}
|
|
|
|
/// Sends a password reset email to the specified email address.
|
|
///
|
|
/// [email] - The email address to send the reset link to
|
|
///
|
|
/// Throws an exception if the operation fails.
|
|
Future<void> resetPassword(String email) async {
|
|
await _authService.resetPassword(email);
|
|
}
|
|
|
|
/// Retrieves user data from Firestore by user ID.
|
|
///
|
|
/// Fetches the user document from the 'users' collection and converts
|
|
/// it to a [User] model.
|
|
///
|
|
/// [uid] - The Firebase user ID to look up
|
|
///
|
|
/// Returns the [User] model if successful, null otherwise.
|
|
Future<User?> getUserFromFirestore(String uid) async {
|
|
try {
|
|
final doc = await _firestore.collection('users').doc(uid).get();
|
|
if (doc.exists) {
|
|
final data = doc.data() as Map<String, dynamic>;
|
|
return User.fromMap({...data, 'id': uid});
|
|
}
|
|
return null;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Helper method to save the FCM token for the authenticated user.
|
|
Future<void> _saveFCMToken(String userId) async {
|
|
try {
|
|
final token = await NotificationService().getFCMToken();
|
|
if (token != null) {
|
|
await _firestore.collection('users').doc(userId).set({
|
|
'fcmToken': token,
|
|
}, SetOptions(merge: true));
|
|
}
|
|
} catch (e, stackTrace) {
|
|
// Non-blocking error
|
|
_errorService.logError(
|
|
'AuthRepository',
|
|
'Error saving FCM token: $e',
|
|
stackTrace,
|
|
);
|
|
}
|
|
}
|
|
}
|