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

8.5 KiB
Raw Permalink Blame History

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 loginlogin_controller.dart:70 stores prefs.setString('userId', response['uuid']). The backend's uuid becomes the locally cached userId.
  2. Anonymous fallbackhome_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.


  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