File size: 5,815 Bytes
ae9649e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# 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
```