swiftops-backend / docs /api /user-profile /USER_PROFILE_PROJECT_CONTEXT.md
kamau1's picture
feat: hub tracking and last active project preference
ae9649e
# 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
```