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

-- 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:

{
  "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):

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):

{
  "last_active_project_id": null
}

Frontend Implementation

On Login

// 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

// 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

// 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

{
  "detail": "You are not assigned to this project"
}

404 Not Found - Project doesn't exist

{
  "detail": "Project not found"
}

400 Bad Request - Invalid UUID format

{
  "detail": "Invalid project ID format"
}

Platform Admin Handling

Platform admins don't have project assignments - they oversee the entire platform:

// 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:

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:

# 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