feat: Migrate staff profile features from Data Connect to V2 REST API
- Removed data_connect package from mobile pubspec.yaml. - Added documentation for V2 profile migration status and QA findings. - Implemented new session management with ClientSessionStore and StaffSessionStore. - Created V2SessionService for handling user sessions via the V2 API. - Developed use cases for cancelling late worker assignments and submitting worker reviews. - Added arguments and use cases for payment chart retrieval and profile completion checks. - Implemented repository interfaces and their implementations for staff main and profile features. - Ensured proper error handling and validation in use cases.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: krow-mobile-development-rules
|
||||
description: Enforce KROW mobile app development standards including file structure, naming conventions, logic placement boundaries, localization, Data Connect integration, and prototype migration rules. Use this skill whenever working on KROW Flutter mobile features, creating new packages, implementing BLoCs, integrating with backend, or migrating from prototypes. Critical for maintaining clean architecture and preventing architectural degradation.
|
||||
description: Enforce KROW mobile app development standards including file structure, naming conventions, logic placement boundaries, localization, V2 REST API integration, and prototype migration rules. Use this skill whenever working on KROW Flutter mobile features, creating new packages, implementing BLoCs, integrating with backend, or migrating from prototypes. Critical for maintaining clean architecture and preventing architectural degradation.
|
||||
---
|
||||
|
||||
# KROW Mobile Development Rules
|
||||
@@ -11,7 +11,7 @@ These rules are **NON-NEGOTIABLE** enforcement guidelines for the KROW mobile ap
|
||||
|
||||
- Creating new mobile features or packages
|
||||
- Implementing BLoCs, Use Cases, or Repositories
|
||||
- Integrating with Firebase Data Connect backend
|
||||
- Integrating with V2 REST API backend
|
||||
- Migrating code from prototypes
|
||||
- Reviewing mobile code for compliance
|
||||
- Setting up new feature modules
|
||||
@@ -186,15 +186,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
```dart
|
||||
// profile_repository_impl.dart
|
||||
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||
ProfileRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
final BaseApiService _apiService;
|
||||
|
||||
@override
|
||||
Future<Staff> getProfile(String id) async {
|
||||
final response = await dataConnect.getStaffById(id: id).execute();
|
||||
// Data transformation happens here
|
||||
return Staff(
|
||||
id: response.data.staff.id,
|
||||
name: response.data.staff.name,
|
||||
// Map Data Connect model to Domain entity
|
||||
final ApiResponse response = await _apiService.get(
|
||||
V2ApiEndpoints.staffProfile(id),
|
||||
);
|
||||
// Data transformation happens here
|
||||
return Staff.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -252,19 +254,19 @@ Modular.to.pop(); // ← Can crash if stack is empty
|
||||
|
||||
**PATTERN:** All navigation MUST have fallback to Home page. Safe extensions automatically handle this.
|
||||
|
||||
### Session Management → DataConnectService + SessionHandlerMixin
|
||||
### Session Management → V2SessionService + SessionHandlerMixin
|
||||
|
||||
**✅ CORRECT:**
|
||||
```dart
|
||||
// In main.dart:
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
|
||||
// Initialize session listener (pick allowed roles for app)
|
||||
DataConnectService.instance.initializeAuthListener(
|
||||
V2SessionService.instance.initializeAuthListener(
|
||||
allowedRoles: ['STAFF', 'BOTH'], // for staff app
|
||||
);
|
||||
|
||||
|
||||
runApp(
|
||||
SessionListener( // Wraps entire app
|
||||
child: ModularApp(module: AppModule(), child: AppWidget()),
|
||||
@@ -274,28 +276,24 @@ void main() async {
|
||||
|
||||
// In repository:
|
||||
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||
final DataConnectService _service = DataConnectService.instance;
|
||||
|
||||
ProfileRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
final BaseApiService _apiService;
|
||||
|
||||
@override
|
||||
Future<Staff> getProfile(String id) async {
|
||||
// _service.run() handles:
|
||||
// - Auth validation
|
||||
// - Token refresh (if <5 min to expiry)
|
||||
// - Error handling with 3 retries
|
||||
return await _service.run(() async {
|
||||
final response = await _service.connector
|
||||
.getStaffById(id: id)
|
||||
.execute();
|
||||
return _mapToStaff(response.data.staff);
|
||||
});
|
||||
final ApiResponse response = await _apiService.get(
|
||||
V2ApiEndpoints.staffProfile(id),
|
||||
);
|
||||
return Staff.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PATTERN:**
|
||||
- **SessionListener** widget wraps app and shows dialogs for session errors
|
||||
- **SessionHandlerMixin** in `DataConnectService` provides automatic token refresh
|
||||
- **3-attempt retry logic** with exponential backoff (1s → 2s → 4s)
|
||||
- **V2SessionService** provides automatic token refresh and auth management
|
||||
- **ApiService** handles HTTP requests with automatic auth headers
|
||||
- **Role validation** configurable per app
|
||||
|
||||
## 4. Localization Integration (core_localization)
|
||||
@@ -372,7 +370,7 @@ class AppModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [
|
||||
LocalizationModule(), // ← Required
|
||||
DataConnectModule(),
|
||||
CoreModule(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -387,44 +385,51 @@ runApp(
|
||||
);
|
||||
```
|
||||
|
||||
## 5. Data Connect Integration
|
||||
## 5. V2 API Integration
|
||||
|
||||
All backend access goes through `DataConnectService`.
|
||||
All backend access goes through `ApiService` with `V2ApiEndpoints`.
|
||||
|
||||
### Repository Pattern
|
||||
|
||||
**Step 1:** Define interface in feature domain:
|
||||
**Step 1:** Define interface in feature domain (optional — feature-level domain layer is optional if entities from `krow_domain` suffice):
|
||||
```dart
|
||||
// domain/repositories/profile_repository_interface.dart
|
||||
abstract interface class ProfileRepositoryInterface {
|
||||
Future<Staff> getProfile(String id);
|
||||
Future<bool> updateProfile(Staff profile);
|
||||
// domain/repositories/shifts_repository_interface.dart
|
||||
abstract interface class ShiftsRepositoryInterface {
|
||||
Future<List<AssignedShift>> getAssignedShifts();
|
||||
Future<AssignedShift> getShiftById(String id);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2:** Implement using `DataConnectService.run()`:
|
||||
**Step 2:** Implement using `ApiService` + `V2ApiEndpoints`:
|
||||
```dart
|
||||
// data/repositories_impl/profile_repository_impl.dart
|
||||
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||
final DataConnectService _service = DataConnectService.instance;
|
||||
|
||||
// data/repositories_impl/shifts_repository_impl.dart
|
||||
class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
||||
ShiftsRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
final BaseApiService _apiService;
|
||||
|
||||
@override
|
||||
Future<Staff> getProfile(String id) async {
|
||||
return await _service.run(() async {
|
||||
final response = await _service.connector
|
||||
.getStaffById(id: id)
|
||||
.execute();
|
||||
return _mapToStaff(response.data.staff);
|
||||
});
|
||||
Future<List<AssignedShift>> getAssignedShifts() async {
|
||||
final ApiResponse response = await _apiService.get(V2ApiEndpoints.staffShiftsAssigned);
|
||||
final List<dynamic> items = response.data['items'] as List<dynamic>;
|
||||
return items.map((dynamic json) => AssignedShift.fromJson(json as Map<String, dynamic>)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AssignedShift> getShiftById(String id) async {
|
||||
final ApiResponse response = await _apiService.get(V2ApiEndpoints.staffShift(id));
|
||||
return AssignedShift.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits of `_service.run()`:**
|
||||
- ✅ Automatic auth validation
|
||||
- ✅ Token refresh if needed
|
||||
- ✅ 3-attempt retry with exponential backoff
|
||||
- ✅ Consistent error handling
|
||||
### Key Conventions
|
||||
|
||||
- **Domain entities** have `fromJson` / `toJson` factory methods for serialization
|
||||
- **Status fields** use enums from `krow_domain` (e.g., `ShiftStatus`, `OrderStatus`)
|
||||
- **Money** is represented in cents as `int` (never `double`)
|
||||
- **Timestamps** are `DateTime` objects (parsed from ISO 8601 strings)
|
||||
- **Feature-level domain layer** is optional when `krow_domain` entities cover the need
|
||||
|
||||
### Session Store Pattern
|
||||
|
||||
@@ -448,7 +453,7 @@ ClientSessionStore.instance.setSession(
|
||||
);
|
||||
```
|
||||
|
||||
**Lazy Loading:** If session is null, fetch via `getStaffById()` or `getBusinessById()` and update store.
|
||||
**Lazy Loading:** If session is null, fetch via the appropriate `ApiService.get()` endpoint and update store.
|
||||
|
||||
## 6. Prototype Migration Rules
|
||||
|
||||
@@ -462,7 +467,7 @@ When migrating from `prototypes/`:
|
||||
### ❌ MUST REJECT & REFACTOR
|
||||
- `GetX`, `Provider`, or `MVC` patterns
|
||||
- Any state management not using BLoC
|
||||
- Direct HTTP calls (must use Data Connect)
|
||||
- Direct HTTP calls (must use ApiService with V2ApiEndpoints)
|
||||
- Hardcoded colors/typography (must use design system)
|
||||
- Global state variables
|
||||
- Navigation without Modular
|
||||
@@ -491,13 +496,12 @@ If requirements are unclear:
|
||||
|
||||
### DO NOT
|
||||
- Add 3rd party packages without checking `apps/mobile/packages/core` first
|
||||
- Add `firebase_auth` or `firebase_data_connect` to Feature packages (they belong in `data_connect` only)
|
||||
- Use `addSingleton` for BLoCs (always use `add` method in Modular)
|
||||
- Add `firebase_auth` or `firebase_data_connect` to Feature packages (they belong in `core` only)
|
||||
|
||||
### DO
|
||||
- Use `DataConnectService.instance` for backend operations
|
||||
- Use `ApiService` with `V2ApiEndpoints` for backend operations
|
||||
- Use Flutter Modular for dependency injection
|
||||
- Register BLoCs with `i.addSingleton<CubitType>(() => CubitType(...))`
|
||||
- Register BLoCs with `i.add<CubitType>(() => CubitType(...))` (transient)
|
||||
- Register Use Cases as factories or singletons as needed
|
||||
|
||||
## 9. Error Handling Pattern
|
||||
@@ -516,15 +520,12 @@ class InvalidCredentialsFailure extends AuthFailure {
|
||||
|
||||
### Repository Error Mapping
|
||||
```dart
|
||||
// Map Data Connect exceptions to Domain failures
|
||||
// Map API errors to Domain failures using ApiErrorHandler
|
||||
try {
|
||||
final response = await dataConnect.query();
|
||||
return Right(response);
|
||||
} on DataConnectException catch (e) {
|
||||
if (e.message.contains('unauthorized')) {
|
||||
return Left(InvalidCredentialsFailure());
|
||||
}
|
||||
return Left(ServerFailure(e.message));
|
||||
final response = await _apiService.get(V2ApiEndpoints.staffProfile(id));
|
||||
return Right(Staff.fromJson(response.data as Map<String, dynamic>));
|
||||
} catch (e) {
|
||||
return Left(ApiErrorHandler.mapToFailure(e));
|
||||
}
|
||||
```
|
||||
|
||||
@@ -579,7 +580,7 @@ testWidgets('shows loading indicator when logging in', (tester) async {
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
- Test full feature flows end-to-end with Data Connect
|
||||
- Test full feature flows end-to-end with V2 API
|
||||
- Use dependency injection to swap implementations if needed
|
||||
|
||||
## 11. Clean Code Principles
|
||||
@@ -635,12 +636,12 @@ Before merging any mobile feature code:
|
||||
- [ ] Zero analyzer warnings
|
||||
|
||||
### Integration
|
||||
- [ ] Data Connect queries via `_service.run()`
|
||||
- [ ] V2 API calls via `ApiService` + `V2ApiEndpoints`
|
||||
- [ ] Error handling with domain failures
|
||||
- [ ] Proper dependency injection in modules
|
||||
|
||||
## Summary
|
||||
|
||||
The key principle: **Clean Architecture with zero tolerance for violations.** Business logic in Use Cases, state in BLoCs, data access in Repositories, UI in Widgets. Features are isolated, backend is centralized, localization is mandatory, and design system is immutable.
|
||||
The key principle: **Clean Architecture with zero tolerance for violations.** Business logic in Use Cases, state in BLoCs, data access in Repositories (via `ApiService` + `V2ApiEndpoints`), UI in Widgets. Features are isolated, backend access is centralized through the V2 REST API layer, localization is mandatory, and design system is immutable.
|
||||
|
||||
When in doubt, refer to existing features following these patterns or ask for clarification. It's better to ask than to introduce architectural debt.
|
||||
|
||||
Reference in New Issue
Block a user