feat(user/realunit): expose user capabilities + structured ALREADY_REGISTERED status#3733
Open
Blume1977 wants to merge 2 commits into
Open
feat(user/realunit): expose user capabilities + structured ALREADY_REGISTERED status#3733Blume1977 wants to merge 2 commits into
Blume1977 wants to merge 2 commits into
Conversation
2 tasks
Blume1977
added a commit
to Blume1977/realunit-app
that referenced
this pull request
May 21, 2026
…tion status Wave 3.2 of the API-as-Decision-Authority audit (`docs/api-authority-plan.md`), companion to API PR DFXswiss/api#3733. The app's settings + registration cubits used to derive UI gating from KYC step status and silently swallow 'already registered' 400s as success. Both signals are now first-class fields on the API response. DTO mirrors ----------- - `UserDto` (`/v2/user`) gains `capabilities: UserCapabilitiesDto` with `canEditName / canEditMail / canEditPhone / canEditAddress / supportAvailable`. All flags default to `false` so a pre-#3733 backend degrades to 'no edit affordances' rather than offering an action the backend would reject. - `RegistrationStatus.alreadyRegistered` mirrors the new `RealUnitRegistrationStatus.ALREADY_REGISTERED` enum value. Consumers --------- - `settings_contact_cubit` reads `userDto.capabilities.supportAvailable` instead of `mail != null`. State field renamed `emailSet` → `supportAvailable` for consistency. - `settings_user_data_cubit` now fetches `/v2/user` alongside `/v2/kyc` and exposes `capabilities` on its Success state. The `SettingsUserDataPage` wires each row's `onEdit` to `canEditName / canEditPhone / canEditAddress`; rows without capability omit the Edit button. `_UserDataRow` drops its `statusLabel == null` co-gating — the badge is informational, the capability is the authority. - `settings_edit_name_cubit` drops the `currentStep?.status == inReview` interpretation. The upstream capability gate prevents the cubit from being instantiated when editing is forbidden, and the pending branch now only fires defensively when the session lacks a URL. - `kyc_registration_submit_cubit` no longer treats *any* `ApiException` after a successful sign as `Success(completed)`. Backend rejection of an already-registered wallet is now a structured `Success(alreadyRegistered)` from the API, and `KycCubit.checkKyc` resolves the next step from there. All other ApiExceptions surface as failures as they always should. Tests ----- - `settings_contact_cubit_test`: assertions renamed and now exercise the API `supportAvailable` capability fixture rather than mail presence as a proxy. - `settings_user_data_cubit_test`: every fixture now mocks `kycService.getUser()` (the new third call site) and seeds `capabilities` where relevant. - `settings_edit_name_cubit_test`: "inReview → Pending" case rephrased to "no URL → Pending". The "no URL → Failure" case is inverted to Pending (matches the cubit's new defensive branch). - `kyc_registration_submit_cubit_test`: the "swallow-ApiException-as-success" case is replaced by an explicit "backend returns alreadyRegistered status → Success(alreadyRegistered)" and a complementary "ApiException → Failure" case so the silent-mask pattern doesn't regress. Verification ------------ - `flutter analyze` — clean - `flutter test` — **1417 / 1417 passing**
…GISTERED status Wave 3 of the realunit-app API-as-Decision-Authority plan (`DFXswiss/realunit-app:docs/api-authority-plan.md`). Two changes that let the realunit-app stop interpreting backend state into UI affordances: UserV2Dto.capabilities ---------------------- New `UserCapabilitiesDto` field on the `/v2/user` response. Surfaces per-action flags the realunit-app cubits were re-deriving locally from KYC step status (`settings_user_data_page.dart:239`, `settings_edit_name_cubit.dart:22`, `settings_contact_page.dart:54-67`): - `canEditName` / `canEditAddress`: false once PersonalData is in any review or completed state (data is locked to keep client and KYC attestation aligned). - `canEditMail` / `canEditPhone`: false only on KYC-terminated accounts. - `supportAvailable`: requires a verified mail. `UserDtoMapper.computeCapabilities` mirrors the rules the cubits encode today. A separate app-side PR consumes the field and drops the local interpretation. RealUnitRegistrationStatus.ALREADY_REGISTERED --------------------------------------------- New enum value. `completeRegistration` and `completeRegistrationForWalletAddress` return it instead of throwing `BadRequestException` when the wallet is already registered for the user. The realunit-app currently catches the 400 and treats it as a success — surfacing the success as a structured status removes the "papering over an error" pattern and lets the app distinguish the merge / retry path cleanly from other 400s. Backwards compatibility ----------------------- Both changes are additive. Old clients ignore the new `capabilities` field and continue to derive editability locally. The `ALREADY_REGISTERED` status is a new enum value — existing clients that switch-fall-through will treat it the same as `FORWARDING_FAILED` (no behaviour change worse than a generic failure). Tests ----- - `user-dto.mapper.spec.ts` adds a `mapUser: capabilities` block covering all five flags across happy path, PersonalData-locked, KYC-terminated, and no-mail fixtures. - Existing tests cover the two `return RealUnitRegistrationStatus.ALREADY_REGISTERED` call sites by absence of any new throw (`completeRegistration` / `completeRegistrationForWalletAddress` did not have happy-path tests for the already-registered branch; PR #3731 addresses the idempotency semantics in the same area and adds dedicated coverage). Local verification ------------------ - `npm run type-check` — clean - `npm run lint` — clean - `npm test` — **943 / 943 passing**
28c4a99 to
78e9605
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wave 3 of the realunit-app API-as-Decision-Authority plan (
DFXswiss/realunit-app:docs/api-authority-plan.md). Two additive changes that let the realunit-app stop interpreting backend state into UI affordances.What changes
UserV2Dto.capabilitiesNew
UserCapabilitiesDtofield on the/v2/userresponse. Surfaces per-action flags the realunit-app cubits were re-deriving locally from KYC step status:canEditName/canEditAddress:falseoncePersonalDatais in any review or completed state (data is locked to keep client and KYC attestation aligned).canEditMail/canEditPhone:falseonly on KYC-terminated accounts.supportAvailable: requires a verified mail.UserDtoMapper.computeCapabilitiesmirrors the rules the realunit-app cubits encode today (settings_user_data_page.dart:239,settings_edit_name_cubit.dart:22,settings_contact_page.dart:54-67).RealUnitRegistrationStatus.ALREADY_REGISTEREDNew enum value.
completeRegistrationandcompleteRegistrationForWalletAddressreturn it instead of throwingBadRequestExceptionwhen the wallet is already registered for the user. The realunit-app currently catches the 400 and treats it as a success — surfacing the success as a structured status removes the "papering over an error" pattern and lets the app distinguish the merge / retry path cleanly from other 400s.The signature-mismatch refinement from #3731 is preserved — same wallet + different signature still throws 400; same wallet + same signature is the idempotent ALREADY_REGISTERED path. This PR doesn't conflict with #3731.
What is intentionally NOT in this PR
requiredWorkflowonSellPaymentInfoDtowas considered (audit V9) but BitBox vs software-wallet selection is unavoidably a client-side device fact — the API can't substitute for the client knowing what hardware it has. Will be addressed differently in a future iteration (e.g. by exposingsupportedSignMethodsrather than a single workflow choice).Backwards compatibility
Both changes are additive:
capabilitiesfield and continue to derive editability locally.ALREADY_REGISTEREDis a new enum value — existing switch-fall-through clients treat it the same asFORWARDING_FAILED(no behaviour worse than a generic failure). The 400-throw path that some app builds rely on for the merge flow is gracefully replaced by a successful status (better than what the app currently swallows).Tests
user-dto.mapper.spec.tsadds amapUser: capabilitiesblock covering all five flags across happy path, PersonalData-locked, KYC-terminated, and no-mail fixtures.Local verification
npm run type-check— cleannpm run lint— cleannpm test— 943 / 943 passing (938 baseline + 5 new tests)Test plan (manual, DEV)
GET /v2/userfor a clean Level-20 user →capabilities.canEditName=true.GET /v2/userfor a user with PersonalData inManualReview→capabilities.canEditName=false.GET /v2/userfor a no-mail user →capabilities.supportAvailable=false.POST /v1/realunit/register/walletfor an already-registered wallet →201 { \"status\": \"already_registered\" }(was 400).Companion app PR
Will open immediately on the realunit-app side to consume
capabilities+ALREADY_REGISTERED.