Spaces:
Sleeping
Sleeping
| # User Profile with Project Context - API Update | |
| ## Summary | |
| Updated `/api/v1/auth/me` endpoint to include project context. The `last_active_project_id` is now stored in the `user_preferences` table (not `users` table) since it's a UI preference. | |
| ## Database Changes | |
| **Migration:** `012_add_user_last_active_project.sql` | |
| ```sql | |
| -- Adds last_active_project_id to user_preferences table | |
| ALTER TABLE user_preferences | |
| ADD COLUMN last_active_project_id UUID REFERENCES projects(id) ON DELETE SET NULL; | |
| ``` | |
| ## Updated API Endpoints | |
| ### 1. GET /api/v1/auth/me | |
| **Returns user profile with project context:** | |
| ```json | |
| { | |
| "id": "user_123", | |
| "email": "pm@example.com", | |
| "name": "John Doe", | |
| "role": "project_manager", | |
| "status": "active", | |
| "is_active": true, | |
| "client_id": null, | |
| "contractor_id": "contractor_456", | |
| "created_at": "2025-01-15T10:00:00Z", | |
| "updated_at": "2025-11-19T14:30:00Z", | |
| // NEW FIELDS: | |
| "primary_project": { | |
| "id": "proj_456", | |
| "title": "Safaricom Fiber Rollout" | |
| }, | |
| "assigned_projects": [ | |
| { "id": "proj_456", "title": "Safaricom Fiber Rollout" }, | |
| { "id": "proj_789", "title": "Nairobi West Maintenance" } | |
| ], | |
| "last_active_project_id": "proj_456" | |
| } | |
| ``` | |
| **Project Selection Logic:** | |
| 1. **last_active_project_id** - User's last selected project (from preferences) | |
| 2. **primary_project** - Determined by priority: | |
| - Last active project (if set) | |
| - First project where user is primary manager | |
| - First assigned project (fallback) | |
| 3. **assigned_projects** - All projects user is a member of (via `project_team` table) | |
| ### 2. PUT /api/v1/auth/me/preferences | |
| **Update user preferences (e.g., switch active project):** | |
| ```http | |
| PUT /api/v1/auth/me/preferences | |
| Authorization: Bearer <token> | |
| Content-Type: application/json | |
| { | |
| "last_active_project_id": "proj_789" | |
| } | |
| ``` | |
| **Response:** Returns full UserProfile (same as GET /me) with updated context | |
| **Features:** | |
| - ✅ Validates user is actually assigned to the project | |
| - ✅ Validates project exists and is active | |
| - ✅ Prevents unauthorized project access | |
| - ✅ Logs all changes in audit log | |
| - ✅ Auto-creates user_preferences if missing | |
| - ✅ Returns updated profile immediately | |
| **Clear preference (no active project):** | |
| ```json | |
| { | |
| "last_active_project_id": null | |
| } | |
| ``` | |
| ## Frontend Implementation | |
| ### On Login | |
| ```typescript | |
| // After successful login, call /auth/me | |
| const userProfile = await fetch('/api/v1/auth/me', { | |
| headers: { 'Authorization': `Bearer ${token}` } | |
| }).then(r => r.json()); | |
| // Use primary_project for initial dashboard | |
| const projectId = userProfile.primary_project?.id; | |
| if (projectId) { | |
| // Fetch dashboard data with this project context | |
| const metrics = await fetch(`/api/v1/tickets/stats?project_id=${projectId}`); | |
| } | |
| // Store assigned projects for project switcher dropdown | |
| localStorage.setItem('assigned_projects', JSON.stringify(userProfile.assigned_projects)); | |
| ``` | |
| ### On Project Switch | |
| ```typescript | |
| // User selects different project from dropdown | |
| async function switchProject(newProjectId: string) { | |
| // Update preference on backend | |
| const updatedProfile = await fetch('/api/v1/auth/me/preferences', { | |
| method: 'PUT', | |
| headers: { | |
| 'Authorization': `Bearer ${token}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ last_active_project_id: newProjectId }) | |
| }).then(r => r.json()); | |
| // Reload dashboard with new project context | |
| window.location.href = `/dashboard?project_id=${newProjectId}`; | |
| } | |
| ``` | |
| ### Project Context Across All Pages | |
| ```typescript | |
| // In your app context/store | |
| const { primary_project, assigned_projects } = userProfile; | |
| // Use primary_project.id for all API calls | |
| const tickets = await fetch( | |
| `/api/v1/tickets?project_id=${primary_project.id}` | |
| ); | |
| const finance = await fetch( | |
| `/api/v1/finance/cash-flow?project_id=${primary_project.id}` | |
| ); | |
| ``` | |
| ## Error Handling | |
| **403 Forbidden** - User not assigned to requested project | |
| ```json | |
| { | |
| "detail": "You are not assigned to this project" | |
| } | |
| ``` | |
| **404 Not Found** - Project doesn't exist | |
| ```json | |
| { | |
| "detail": "Project not found" | |
| } | |
| ``` | |
| **400 Bad Request** - Invalid UUID format | |
| ```json | |
| { | |
| "detail": "Invalid project ID format" | |
| } | |
| ``` | |
| ## Platform Admin Handling | |
| **Platform admins don't have project assignments** - they oversee the entire platform: | |
| ```json | |
| // Platform admin's /auth/me response: | |
| { | |
| "id": "admin_123", | |
| "email": "admin@platform.com", | |
| "role": "platform_admin", | |
| // ... other fields ... | |
| "primary_project": null, | |
| "assigned_projects": [], | |
| "last_active_project_id": null | |
| } | |
| ``` | |
| **Attempting to set project preference as platform_admin returns error:** | |
| ```http | |
| PUT /api/v1/auth/me/preferences | |
| { "last_active_project_id": "proj_456" } | |
| // Response: 400 Bad Request | |
| { | |
| "detail": "Platform admins cannot set active project (no project assignments)" | |
| } | |
| ``` | |
| Frontend should: | |
| - Hide project switcher for platform_admin role | |
| - Don't scope API calls to project_id for platform admins | |
| - Show platform-wide dashboard instead of project dashboard | |
| ## Notes | |
| - **Automatic Creation:** If user_preferences doesn't exist, it's auto-created on first preference update | |
| - **Backwards Compatible:** Existing code still works - just ignore new fields if not needed | |
| - **Security:** All project access is validated against project_team membership | |
| - **Performance:** Indexed queries, single DB roundtrip for profile data | |
| - **Audit Trail:** All preference changes logged with old/new values | |
| - **Platform Admin:** Returns null/empty project context, cannot set project preference | |
| ## Migration | |
| Run the migration: | |
| ```bash | |
| # Apply migration | |
| psql -U postgres -d your_database -f migrations/012_add_user_last_active_project.sql | |
| # Rollback if needed | |
| psql -U postgres -d your_database -f migrations/012_add_user_last_active_project_rollback.sql | |
| ``` | |