# Refactoring Plan: Extract Business Logic from Routes ## Goal Remove all business/domain logic from route handlers. Routes should only: 1. Receive HTTP requests 2. Call service methods 3. Map responses using mappers 4. Return HTTP responses ## Current State Analysis ### Routes with Business Logic #### 1. `routes/auth.py` **Business Logic:** - `register()`: Invite validation, user creation, invite marking, role assignment - `get_default_role()`: Database query (should use repository) **Action:** Create `AuthService` with: - `register_user()` - handles entire registration flow - `login_user()` - handles authentication and token creation #### 2. `routes/invites.py` **Business Logic:** - `check_invite()`: Invite validation logic - `get_my_invites()`: Database query + response building - `create_invite()`: Invite creation with collision retry logic - `list_all_invites()`: Query building, filtering, pagination - `revoke_invite()`: Revocation business logic **Action:** Use existing `InviteService` (already exists but not fully used): - Move `check_invite()` logic to `InviteService.check_invite_validity()` - Move `create_invite()` logic to `InviteService.create_invite()` - Move `revoke_invite()` logic to `InviteService.revoke_invite()` - Add `InviteService.get_user_invites()` for `get_my_invites()` - Add `InviteService.list_invites()` for `list_all_invites()` #### 3. `routes/profile.py` **Business Logic:** - `get_godfather_email()`: Database query (should use repository) - `get_profile()`: Data retrieval and response building - `update_profile()`: Validation and field updates **Action:** Create `ProfileService` with: - `get_profile()` - retrieves profile with godfather email - `update_profile()` - validates and updates profile fields #### 4. `routes/availability.py` **Business Logic:** - `get_availability()`: Query, grouping by date, transformation - `set_availability()`: Slot overlap validation, time ordering validation, deletion, creation - `copy_availability()`: Source validation, copying logic, atomic transaction handling **Action:** Create `AvailabilityService` with: - `get_availability_for_range()` - gets and groups availability - `set_availability_for_date()` - validates slots and replaces availability - `copy_availability()` - copies availability from one date to others #### 5. `routes/audit.py` **Business Logic:** - `get_price_history()`: Database query - `fetch_price_now()`: Price fetching, duplicate timestamp handling - `_to_price_history_response()`: Mapping (should use mapper) **Action:** Create `PriceService` with: - `get_recent_prices()` - gets recent price history - `fetch_and_store_price()` - fetches from Bitfinex and stores (handles duplicates) - Move `_to_price_history_response()` to `PriceHistoryMapper` #### 6. `routes/exchange.py` **Business Logic:** - `get_available_slots()`: Query, slot expansion logic - Enum validation (acceptable - this is input validation at route level) **Action:** - Move slot expansion logic to `ExchangeService` or `AvailabilityService` - Keep enum validation in route (it's input validation, not business logic) ## Implementation Plan ### Phase 1: Create Missing Services 1. ✅ `ExchangeService` (already exists) 2. ✅ `InviteService` (already exists, needs expansion) 3. ❌ `AuthService` (needs creation) 4. ❌ `ProfileService` (needs creation) 5. ❌ `AvailabilityService` (needs creation) 6. ❌ `PriceService` (needs creation) ### Phase 2: Expand Existing Services 1. Expand `InviteService`: - Add `get_user_invites()` - Add `list_invites()` with pagination - Ensure all methods use repositories ### Phase 3: Update Routes to Use Services 1. `routes/auth.py` → Use `AuthService` 2. `routes/invites.py` → Use `InviteService` consistently 3. `routes/profile.py` → Use `ProfileService` 4. `routes/availability.py` → Use `AvailabilityService` 5. `routes/audit.py` → Use `PriceService` 6. `routes/exchange.py` → Move slot expansion to service ### Phase 4: Clean Up 1. Remove all direct database queries from routes 2. Remove all business logic from routes 3. Replace all `HTTPException` with custom exceptions 4. Ensure all mappers are used consistently 5. Remove helper functions from routes (move to services/repositories) ## File Structure After Refactoring ``` backend/ ├── routes/ │ ├── auth.py # Only HTTP handling, calls AuthService │ ├── invites.py # Only HTTP handling, calls InviteService │ ├── profile.py # Only HTTP handling, calls ProfileService │ ├── availability.py # Only HTTP handling, calls AvailabilityService │ ├── audit.py # Only HTTP handling, calls PriceService │ └── exchange.py # Only HTTP handling, calls ExchangeService ├── services/ │ ├── __init__.py │ ├── auth.py # NEW: Registration, login logic │ ├── invite.py # EXISTS: Expand with missing methods │ ├── profile.py # NEW: Profile CRUD operations │ ├── availability.py # NEW: Availability management │ ├── price.py # NEW: Price fetching and history │ └── exchange.py # EXISTS: Already good, minor additions ├── repositories/ │ └── ... (already good) └── mappers/ └── ... (add PriceHistoryMapper) ``` ## Detailed Service Specifications ### AuthService ```python class AuthService: async def register_user( self, email: str, password: str, invite_identifier: str ) -> tuple[User, str]: # Returns (user, token) """Register new user with invite validation.""" async def login_user( self, email: str, password: str ) -> tuple[User, str]: # Returns (user, token) """Authenticate user and create token.""" ``` ### ProfileService ```python class ProfileService: async def get_profile(self, user: User) -> ProfileResponse: """Get user profile with godfather email.""" async def update_profile( self, user: User, data: ProfileUpdate ) -> ProfileResponse: """Validate and update profile fields.""" ``` ### AvailabilityService ```python class AvailabilityService: async def get_availability_for_range( self, from_date: date, to_date: date ) -> AvailabilityResponse: """Get availability grouped by date.""" async def set_availability_for_date( self, target_date: date, slots: list[TimeSlot] ) -> AvailabilityDay: """Validate and set availability for a date.""" async def copy_availability( self, source_date: date, target_dates: list[date] ) -> AvailabilityResponse: """Copy availability from source to target dates.""" ``` ### PriceService ```python class PriceService: async def get_recent_prices(self, limit: int = 20) -> list[PriceHistory]: """Get recent price history.""" async def fetch_and_store_price(self) -> PriceHistory: """Fetch price from Bitfinex and store (handles duplicates).""" ``` ### InviteService (Expansion) ```python class InviteService: # Existing methods... async def get_user_invites(self, user_id: int) -> list[Invite]: """Get all invites for a user.""" async def list_invites( self, page: int, per_page: int, status_filter: str | None = None, godfather_id: int | None = None ) -> PaginatedInviteRecords: """List invites with pagination and filtering.""" ``` ## Testing Strategy 1. Ensure all existing tests pass after each service creation 2. Add service-level unit tests 3. Keep route tests focused on HTTP concerns (status codes, response formats) 4. Move business logic tests to service tests ## Migration Order 1. **PriceService** (simplest, least dependencies) 2. **AvailabilityService** (self-contained) 3. **ProfileService** (simple CRUD) 4. **AuthService** (more complex, but isolated) 5. **InviteService expansion** (already exists, just expand) 6. **ExchangeService** (slot expansion logic) ## Success Criteria - ✅ No `await db.execute()` calls in routes - ✅ No business validation logic in routes - ✅ No data transformation logic in routes - ✅ All routes are thin wrappers around service calls - ✅ All tests pass - ✅ Code is more testable and maintainable