Files
velocity-pay-flutter/WORKSPACE_ID_MIGRATION_AUDIT.md
2026-06-22 10:06:51 +02:00

161 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Audit: Migrating from `userId` to `workspaceId`
This document audits every place the project uses `userId` (and `user_id`) so the
migration to a `workspaceId`-scoped model can be done completely. There are
currently **no references to `workspaceId`** anywhere in the codebase — this is a
greenfield rename/replacement.
## How scoping currently works (the mental model)
1. **Source of truth set at login**`login_controller.dart:70` stores
`prefs.setString('userId', response['uuid'])`. The backend's `uuid` becomes
the locally cached `userId`.
2. **Anonymous fallback**`home_screen.dart:127,162` generate a random
`Uuid().v4()` and store it as `userId` when none exists (anonymous/guest flow).
3. **Consumption** — Almost everywhere else reads `prefs.getString("userId")`
and passes it to the backend as:
- a **query param** (`?userId=...` or `_queryParams['userId']`),
- a **request body field** (`BatchActionRequest.userId`, `FormData.userId`),
- a **model field** deserialized from `json['userId']`.
> Migration decision needed (see "Open questions"): is `workspaceId` a NEW value
> the backend returns at login, or simply a rename of the existing `uuid`? And
> will the API now accept/scope by a header (e.g. `X-Workspace-Id`) instead of a
> query param? The plan below assumes a rename + the option to centralize scoping.
---
## Inventory of changes required
### A. Persistence / key name (the keystone)
The SharedPreferences key string `"userId"` is the single most important thing to
change consistently. If the key string is changed in one place but not another,
reads will silently return `null`.
| File | Line | Current | Action |
|---|---|---|---|
| `lib/screens/onboarding/login/login_controller.dart` | 70 | `prefs.setString('userId', response['uuid'])` | Write `workspaceId`. Confirm backend field — may become `response['workspaceId']`. |
| `lib/screens/home/home_screen.dart` | 127, 162 | `prefs.setString("userId", Uuid().v4())` | Rename key to `workspaceId` (anonymous fallback). |
| `lib/screens/home/home_screen.dart` | 126, 130, 151, 152, 161, 165 | `prefs.getString("userId")` | Rename key reads to `workspaceId`. |
| `lib/screens/onboarding/phone/phone_controller.dart` | 48 | `"tempUid": prefs.get("userId")` | Rename key read; confirm whether `tempUid` body field name should change too. |
> Recommendation: introduce a single constant, e.g. `const kWorkspaceIdKey = 'workspaceId';`
> and a small helper (`Prefs.workspaceId`) so the key string is never duplicated again.
> A migration shim should also copy any existing `userId` value to `workspaceId` on
> first launch so logged-in users aren't logged out.
### B. Query-param / URL usages (sent to backend)
| File | Line | Current |
|---|---|---|
| `lib/screens/recipient/recipients_screen.dart` | 69, 271 | `_queryParams['userId'] = prefs.getString("userId")!` |
| `lib/screens/history/history_controller.dart` | 119, 146 | `'userId': userId` (query map) |
| `lib/screens/groups/controllers/group_controller.dart` | 72, 197, 233, 269 | `'userId': userId` and `?userId=$userId` in URLs |
| `lib/screens/groups/controllers/batch_controller.dart` | 369, 528, 584 | `?userId=$userId` in URLs |
| `lib/screens/home/home_controller.dart` | 158 | `...&userId=$userId` in URL |
Action: rename the param key to `workspaceId` (or move scoping into a header — see C).
### C. HTTP layer (`lib/http/http.dart`)
Currently the HTTP wrapper only adds `Authorization: Bearer <token>` headers
(`getHeaders()` around lines 2836). It does **not** know about `userId`.
Action / opportunity: if the backend now scopes by a workspace header, this is the
single best place to inject `X-Workspace-Id` once, eliminating the dozens of
per-call query params. Otherwise, leave the per-call params and just rename them.
### D. Request-body model fields & method params (Dart-side, hand-written)
These are the hand-written declarations and call sites to rename
(`userId` -> `workspaceId`):
- `lib/screens/groups/models/group_batch_model.dart` — fields at lines 9, 90, 117, 134 and constructors 26, 100, 121, 141 (includes `BatchActionRequest.userId`).
- `lib/screens/groups/models/recipient_group_model.dart` — fields 11, 33, 58 and constructors 18, 42, 64.
- `lib/screens/groups/models/batch_item_update_model.dart` — field 38, constructor 40.
- `lib/screens/transactions/transaction_model.dart` — field 53, constructor 101.
- `lib/screens/transaction_controller.dart` — defaults/params 41, 71, 152 (the freezed `FormData.userId`).
- `lib/screens/groups/controllers/group_controller.dart` — method params 56, 118, 123, 193, 229, 244, 257 and internal calls 120, 128, 251.
- `lib/screens/groups/controllers/batch_controller.dart` — method params 363, 466, 494, 522, 545, 581, 598 and `BatchActionRequest(userId: ...)` at 473, 501, 552; recursive call 606.
- `lib/screens/history/history_controller.dart` — method params 110, 141.
- `lib/screens/home/home_controller.dart` — method param 151.
### E. Call sites in screens (read prefs -> pass through)
- `lib/screens/pay/pay_controller.dart` — 101 (`userId: prefs.getString("userId") ?? ''` into FormData).
- `lib/screens/groups/screens/group_create_screen.dart` — 84, 91.
- `lib/screens/groups/screens/group_detail_screen.dart` — 555, 678, 703 (`widget.group.userId`).
- `lib/screens/groups/screens/batch_detail_screen.dart` — local `_userId` at 36, 88 and uses at 90, 139, 155, 434, 487, 675, 688, 1438.
- `lib/screens/groups/screens/create_group_from_file_screen.dart` — 68, 72.
- `lib/screens/groups/screens/groups_screen.dart` — 33, 39, 40, 44, 46, 52, 75.
- `lib/screens/groups/screens/batch_create_screen.dart` — 92, 96, 118, 133, 157.
- `lib/screens/history/history_screen.dart` — 67, 74, 230, 330, 354, 499, 512.
### F. Generated files (DO NOT hand-edit — regenerate)
These contain `userId` derived from `@JsonKey`/freezed annotations in the
hand-written sources above. After renaming the source fields (and the JSON keys),
regenerate with build_runner:
- `lib/screens/transaction_controller.g.dart`
- `lib/screens/transaction_controller.freezed.dart`
- `lib/screens/groups/models/batch_item_update_model.g.dart`
- `lib/screens/groups/models/recipient_group_model.g.dart`
- `lib/screens/groups/models/group_batch_model.g.dart`
- `lib/screens/transactions/transaction_model.g.dart`
Regenerate command:
```
dart run build_runner build --delete-conflicting-outputs
```
---
## JSON wire-key decision (critical)
The generated serializers currently read/write the JSON key `'userId'`
(e.g. `json['userId']`, `'userId': instance.userId`). You must decide whether the
**backend JSON contract** also changes to `workspaceId`:
- If the API now expects/returns `workspaceId`: rename the Dart field AND let the
serializer use the new key (or add `@JsonKey(name: 'workspaceId')`).
- If the API still uses `userId` on the wire but you only want the Dart field
renamed: keep the wire key with `@JsonKey(name: 'userId')` on the renamed field.
This must be coordinated with the backend team before regenerating.
---
## Recommended migration order
1. Confirm backend contract (see Open questions) — header vs query param, and the
JSON key name.
2. Add a `kWorkspaceIdKey` constant + prefs helper, plus a one-time migration shim
that copies existing `userId` -> `workspaceId`.
3. (Optional but recommended) Inject `X-Workspace-Id` centrally in `http.dart`.
4. Rename hand-written model fields / method params (sections D & E).
5. Update query-param/URL usages (section B), or remove them if moved to header.
6. Update the prefs key reads/writes (section A).
7. Regenerate build_runner outputs (section F).
8. `flutter analyze` and test the anonymous flow, login flow, history, groups,
batches, recipients, and pay flows end-to-end.
## Open questions for the team
- Is `workspaceId` a brand-new value from the backend (e.g. `response['workspaceId']`),
or just a rename of the existing `uuid`/`userId`?
- Should scoping move to a request **header** (`X-Workspace-Id`) so it can be set
once in `http.dart`, instead of ~15 per-call query params/body fields?
- Does the JSON wire contract change to `workspaceId`, or stay `userId`?
- For the anonymous/guest flow (`home_screen.dart`), is a client-generated
`Uuid().v4()` still acceptable as a `workspaceId`, or must the backend mint it?
- Does `phone_controller`'s `tempUid` body key need renaming too?
## Summary counts
- Non-generated `.dart` files to edit: **21**
- Generated files to regenerate: **6**
- Total non-generated `userId`/`user_id` occurrences: **~112**
- Existing `workspaceId` references: **0**