# 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 ` headers (`getHeaders()` around lines 28–36). 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**