Refactor PersonalInfoRepository to use Firebase Auth for user profile retrieval and remove mock implementation

This commit is contained in:
Achintha Isuru
2026-01-26 20:35:34 -05:00
committed by José Salazar
parent fef3289648
commit 582855d86b
7 changed files with 52 additions and 119 deletions

View File

@@ -1,3 +1,4 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -14,27 +15,36 @@ import '../../domain/repositories/personal_info_repository_interface.dart';
/// - Containing no business logic /// - Containing no business logic
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface { class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
final ExampleConnector _dataConnect; final ExampleConnector _dataConnect;
final FirebaseAuth _firebaseAuth;
/// Creates a [PersonalInfoRepositoryImpl]. /// Creates a [PersonalInfoRepositoryImpl].
/// ///
/// Requires the Firebase Data Connect connector instance. /// Requires the Firebase Data Connect connector instance and Firebase Auth.
PersonalInfoRepositoryImpl({ PersonalInfoRepositoryImpl({
required ExampleConnector dataConnect, required ExampleConnector dataConnect,
}) : _dataConnect = dataConnect; required FirebaseAuth firebaseAuth,
}) : _dataConnect = dataConnect,
_firebaseAuth = firebaseAuth;
@override @override
Future<Staff> getStaffProfile(String staffId) async { Future<Staff> getStaffProfile() async {
// Query staff data from Firebase Data Connect final user = _firebaseAuth.currentUser;
final QueryResult<GetStaffByIdData, GetStaffByIdVariables> result = if (user == null) {
await _dataConnect.getStaffById(id: staffId).execute(); throw Exception('User not authenticated');
final staff = result.data.staff;
if (staff == null) {
throw Exception('Staff profile not found for ID: $staffId');
} }
// Query staff data from Firebase Data Connect
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables> result =
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
if (result.data.staffs.isEmpty) {
throw Exception('Staff profile not found for User ID: ${user.uid}');
}
final rawStaff = result.data.staffs.first;
// Map from data_connect DTO to domain entity // Map from data_connect DTO to domain entity
return _mapToStaffEntity(staff); return _mapToStaffEntity(rawStaff);
} }
@override @override
@@ -54,7 +64,7 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
} }
// Fetch the updated staff profile to return complete entity // Fetch the updated staff profile to return complete entity
return getStaffProfile(staff.id); return getStaffProfile();
} }
@override @override
@@ -69,16 +79,22 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
/// Maps a data_connect Staff DTO to a domain Staff entity. /// Maps a data_connect Staff DTO to a domain Staff entity.
/// ///
/// This mapping isolates the domain from data layer implementation details. /// This mapping isolates the domain from data layer implementation details.
Staff _mapToStaffEntity(GetStaffByIdStaff dto) { Staff _mapToStaffEntity(GetStaffByUserIdStaffs dto) {
return Staff( return Staff(
id: dto.id, id: dto.id,
authProviderId: dto.userId, authProviderId: dto.userId,
name: dto.fullName, name: dto.fullName,
email: dto.email ?? '', email: dto.email ?? '',
phone: dto.phone, phone: dto.phone,
status: StaffStatus.active, // TODO: Map from actual status field when available
address: dto.addres,
avatar: dto.photoUrl, avatar: dto.photoUrl,
status: StaffStatus.active,
address: dto.addres,
totalShifts: dto.totalShifts,
averageRating: dto.averageRating,
onTimeRate: dto.onTimeRate,
noShowCount: dto.noShowCount,
cancellationCount: dto.cancellationCount,
reliabilityScore: dto.reliabilityScore,
); );
} }
} }

View File

@@ -1,59 +0,0 @@
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/personal_info_repository_interface.dart';
/// Mock implementation of [PersonalInfoRepositoryInterface].
///
/// This mock repository returns hardcoded data for development
/// and will be replaced with [PersonalInfoRepositoryImpl] when
/// Firebase Data Connect is fully configured.
///
/// Following Clean Architecture, this mock:
/// - Implements the domain repository interface
/// - Returns domain entities (Staff)
/// - Simulates async operations with delays
/// - Provides realistic test data
class PersonalInfoRepositoryMock implements PersonalInfoRepositoryInterface {
// Simulated in-memory storage
Staff? _cachedStaff;
@override
Future<Staff> getStaffProfile(String staffId) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 500));
// Return cached staff or create mock data
return _cachedStaff ??
const Staff(
id: 'mock-staff-1',
authProviderId: 'mock-auth-1',
name: 'Krower',
email: 'worker@krow.com',
phone: '',
status: StaffStatus.active,
address: 'Montreal, Quebec',
avatar: null,
);
}
@override
Future<Staff> updateStaffProfile(Staff staff) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 800));
// Store in cache
_cachedStaff = staff;
// Return the updated staff
return staff;
}
@override
Future<String> uploadProfilePhoto(String filePath) async {
// Simulate upload delay
await Future.delayed(const Duration(seconds: 2));
// Return a mock URL
return 'https://example.com/photos/${DateTime.now().millisecondsSinceEpoch}.jpg';
}
}

View File

@@ -8,10 +8,10 @@ import 'package:krow_domain/krow_domain.dart';
/// Implementations must delegate all data operations through /// Implementations must delegate all data operations through
/// the data_connect layer, following Clean Architecture principles. /// the data_connect layer, following Clean Architecture principles.
abstract interface class PersonalInfoRepositoryInterface { abstract interface class PersonalInfoRepositoryInterface {
/// Retrieves the staff profile for the specified staff ID. /// Retrieves the staff profile for the current authenticated user.
/// ///
/// Returns the complete [Staff] entity with all profile information. /// Returns the complete [Staff] entity with all profile information.
Future<Staff> getStaffProfile(String staffId); Future<Staff> getStaffProfile();
/// Updates the staff profile information. /// Updates the staff profile information.
/// ///

View File

@@ -2,23 +2,12 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../repositories/personal_info_repository_interface.dart'; import '../repositories/personal_info_repository_interface.dart';
/// Arguments for getting staff profile information.
class GetPersonalInfoArguments extends UseCaseArgument {
/// The staff member's ID.
final String staffId;
const GetPersonalInfoArguments({required this.staffId});
@override
List<Object?> get props => [staffId];
}
/// Use case for retrieving staff profile information. /// Use case for retrieving staff profile information.
/// ///
/// This use case fetches the complete staff profile from the repository, /// This use case fetches the complete staff profile from the repository,
/// which delegates to the data_connect layer for data access. /// which delegates to the data_connect layer for data access.
class GetPersonalInfoUseCase class GetPersonalInfoUseCase
implements UseCase<GetPersonalInfoArguments, Staff> { implements NoInputUseCase<Staff> {
final PersonalInfoRepositoryInterface _repository; final PersonalInfoRepositoryInterface _repository;
/// Creates a [GetPersonalInfoUseCase]. /// Creates a [GetPersonalInfoUseCase].
@@ -27,7 +16,7 @@ class GetPersonalInfoUseCase
GetPersonalInfoUseCase(this._repository); GetPersonalInfoUseCase(this._repository);
@override @override
Future<Staff> call(GetPersonalInfoArguments arguments) { Future<Staff> call() {
return _repository.getStaffProfile(arguments.staffId); return _repository.getStaffProfile();
} }
} }

View File

@@ -2,23 +2,12 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../repositories/personal_info_repository_interface.dart'; import '../repositories/personal_info_repository_interface.dart';
/// Arguments for updating staff profile information.
class UpdatePersonalInfoArguments extends UseCaseArgument {
/// The staff entity with updated information.
final Staff staff;
const UpdatePersonalInfoArguments({required this.staff});
@override
List<Object?> get props => [staff];
}
/// Use case for updating staff profile information. /// Use case for updating staff profile information.
/// ///
/// This use case updates the staff profile information /// This use case updates the staff profile information
/// through the repository, which delegates to the data_connect layer. /// through the repository, which delegates to the data_connect layer.
class UpdatePersonalInfoUseCase class UpdatePersonalInfoUseCase
implements UseCase<UpdatePersonalInfoArguments, Staff> { implements UseCase<Staff, Staff> {
final PersonalInfoRepositoryInterface _repository; final PersonalInfoRepositoryInterface _repository;
/// Creates an [UpdatePersonalInfoUseCase]. /// Creates an [UpdatePersonalInfoUseCase].
@@ -27,7 +16,7 @@ class UpdatePersonalInfoUseCase
UpdatePersonalInfoUseCase(this._repository); UpdatePersonalInfoUseCase(this._repository);
@override @override
Future<Staff> call(UpdatePersonalInfoArguments arguments) { Future<Staff> call(Staff arguments) {
return _repository.updateStaffProfile(arguments.staff); return _repository.updateStaffProfile(arguments);
} }
} }

View File

@@ -1,5 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/usecases/get_personal_info_usecase.dart'; import '../../domain/usecases/get_personal_info_usecase.dart';
@@ -16,18 +17,15 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
implements Disposable { implements Disposable {
final GetPersonalInfoUseCase _getPersonalInfoUseCase; final GetPersonalInfoUseCase _getPersonalInfoUseCase;
final UpdatePersonalInfoUseCase _updatePersonalInfoUseCase; final UpdatePersonalInfoUseCase _updatePersonalInfoUseCase;
final String _staffId;
/// Creates a [PersonalInfoBloc]. /// Creates a [PersonalInfoBloc].
/// ///
/// Requires the staff ID to load and update the correct profile. /// Requires the use cases to load and update the profile.
PersonalInfoBloc({ PersonalInfoBloc({
required GetPersonalInfoUseCase getPersonalInfoUseCase, required GetPersonalInfoUseCase getPersonalInfoUseCase,
required UpdatePersonalInfoUseCase updatePersonalInfoUseCase, required UpdatePersonalInfoUseCase updatePersonalInfoUseCase,
required String staffId,
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase, }) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
_updatePersonalInfoUseCase = updatePersonalInfoUseCase, _updatePersonalInfoUseCase = updatePersonalInfoUseCase,
_staffId = staffId,
super(const PersonalInfoState()) { super(const PersonalInfoState()) {
on<PersonalInfoLoadRequested>(_onLoadRequested); on<PersonalInfoLoadRequested>(_onLoadRequested);
on<PersonalInfoFieldUpdated>(_onFieldUpdated); on<PersonalInfoFieldUpdated>(_onFieldUpdated);
@@ -42,9 +40,7 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
) async { ) async {
emit(state.copyWith(status: PersonalInfoStatus.loading)); emit(state.copyWith(status: PersonalInfoStatus.loading));
try { try {
final Staff staff = await _getPersonalInfoUseCase( final Staff staff = await _getPersonalInfoUseCase();
GetPersonalInfoArguments(staffId: _staffId),
);
emit(state.copyWith( emit(state.copyWith(
status: PersonalInfoStatus.loaded, status: PersonalInfoStatus.loaded,
staff: staff, staff: staff,
@@ -115,7 +111,7 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
emit(state.copyWith(status: PersonalInfoStatus.saving)); emit(state.copyWith(status: PersonalInfoStatus.saving));
try { try {
final Staff updatedStaff = await _updatePersonalInfoUseCase( final Staff updatedStaff = await _updatePersonalInfoUseCase(
UpdatePersonalInfoArguments(staff: state.staff!), state.staff!,
); );
emit(state.copyWith( emit(state.copyWith(
status: PersonalInfoStatus.saved, status: PersonalInfoStatus.saved,

View File

@@ -1,7 +1,9 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'data/repositories/personal_info_repository_mock.dart'; import 'data/repositories/personal_info_repository_impl.dart';
import 'domain/repositories/personal_info_repository_interface.dart'; import 'domain/repositories/personal_info_repository_interface.dart';
import 'domain/usecases/get_personal_info_usecase.dart'; import 'domain/usecases/get_personal_info_usecase.dart';
import 'domain/usecases/update_personal_info_usecase.dart'; import 'domain/usecases/update_personal_info_usecase.dart';
@@ -14,17 +16,19 @@ import 'presentation/pages/personal_info_page.dart';
/// personal information functionality following Clean Architecture. /// personal information functionality following Clean Architecture.
/// ///
/// The module: /// The module:
/// - Registers repository implementations (mock for now, will use real impl later) /// - Registers repository implementations
/// - Registers use cases that contain business logic /// - Registers use cases that contain business logic
/// - Registers BLoC for state management /// - Registers BLoC for state management
/// - Defines routes for navigation /// - Defines routes for navigation
class StaffProfileInfoModule extends Module { class StaffProfileInfoModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repository - using mock for now // Repository
// TODO: Replace with PersonalInfoRepositoryImpl when Firebase Data Connect is configured
i.addLazySingleton<PersonalInfoRepositoryInterface>( i.addLazySingleton<PersonalInfoRepositoryInterface>(
PersonalInfoRepositoryMock.new, () => PersonalInfoRepositoryImpl(
dataConnect: ExampleConnector.instance,
firebaseAuth: FirebaseAuth.instance,
),
); );
// Use Cases - delegate business logic to repository // Use Cases - delegate business logic to repository
@@ -36,12 +40,10 @@ class StaffProfileInfoModule extends Module {
); );
// BLoC - manages presentation state // BLoC - manages presentation state
// TODO: Get actual staffId from authentication state
i.addLazySingleton<PersonalInfoBloc>( i.addLazySingleton<PersonalInfoBloc>(
() => PersonalInfoBloc( () => PersonalInfoBloc(
getPersonalInfoUseCase: i.get<GetPersonalInfoUseCase>(), getPersonalInfoUseCase: i.get<GetPersonalInfoUseCase>(),
updatePersonalInfoUseCase: i.get<UpdatePersonalInfoUseCase>(), updatePersonalInfoUseCase: i.get<UpdatePersonalInfoUseCase>(),
staffId: 'mock-staff-1', // TODO: Get from auth
), ),
); );
} }