feat: Add organizations support - #312
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces organization-account support across the network layer, persistence layer (Room), and core managers so transfers can be created/finalized and queried in the context of an organization account, and organization metadata can be fetched and stored locally.
Changes:
- Add org-aware API routes for creating and completing transfers, plus a new
users/merequest/repository andMyUsermodel to retrieve organization accounts. - Extend Room schema (v2) with organization tables and add
organizationAccountIdto transfers, wiring org selection intoAccountManager/TransferManager. - Thread
organizationAccountIdthrough upload/transfer flows and adjust tests and DI accordingly.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/utils/ApiRoutes.kt | Add org-scoped routes for transfer creation/completion; add users/me route constant. |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/requests/v2/UserInfoRequests.kt | New v2 request to fetch current user info (users/me). |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/requests/v2/UploadRequest.kt | Thread organizationAccountId into create/finalize/abort transfer requests. |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/repositories/UserInfoRepository.kt | New repository for MyUser retrieval with error mapping. |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/repositories/UploadV2Repository.kt | Propagate organizationAccountId through upload repository APIs. |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/models/upload/request/v2/CreateTransfer.kt | Minor payload shape tweak (trailing comma); leftover commented field. |
| STNetwork/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/network/models/MyUser.kt | New API model for org accounts and default account ID. |
| STDatabase/src/commonTest/kotlin/com/infomaniak/multiplatform_swisstransfer/database/v2/TransfersTest.kt | Update DAO calls/signatures and adjust count test to global count flow. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/RealmProvider.kt | Attempt to move Realm open/close work to IO dispatcher. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/models/transfers/v2/TransferDB.kt | Add nullable organizationAccountId to transfer entity. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/models/SelectedOrganizationAccount.kt | New entity to persist last selected org per user. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/models/OrganizationAccount.kt | New entity to persist organization account metadata and limits. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/DatabaseProvider.kt | Bump Room DB version to 2 and add auto-migration + org DAOs/entities. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/dao/TransferDao.kt | Add org-aware queries and global flows for transfers + counts. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/dao/OrganizationsDao.kt | New DAO for reading/writing org accounts and last selection. |
| STDatabase/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/database/controllers/TransferController.kt | Make transfer count flow accept nullable direction. |
| STDatabase/schemas/com.infomaniak.multiplatform_swisstransfer.database.AppDatabase/2.json | Add schema snapshot for Room v2 (org tables + orgAccountId column). |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/SwissTransferInjection.kt | Wire UserInfoRepository into injection. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/mappers/TransferApiV2Ext.kt | Map organizationAccountId from API transfer into DB model. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/mappers/OrganizationAccountMapper.kt | Map org accounts from MyUser API model to Room entities. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/UploadV2Manager.kt | Pass org ID through transfer creation/finalization/cancellation and persist it. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/TransferManager.kt | Add org-aware transfer listing logic and new “has any transfer” flow. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt | Add org selection APIs and refresh org accounts on user load. |
| STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/data/STUser.kt | Formatting-only change to AuthUser definition. |
| STCommon/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/common/interfaces/upload/UploadSessionRequest.kt | Add organizationAccountId to upload session request contract. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This will then replace the getTransfersCount function (on Android), and the getAllTransfers function (on iOS) for the following use-case: Prompting the user to create their first transfer when the emitted value is `false`.
This commit also makes selectedOrganizationAccountIdForUser internal
b5a44cf to
452e46f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt:128
- Selecting the personal account (
null) deletes the row, making that explicit choice indistinguishable from “no selection saved.” On the next authenticatedloadUser, line 196 seesnulland replaces the user's choice withdefaultOrganizationAccountId, so users whose API default is an organization cannot persist a switch back to personal. Persist an explicit nullable selection separately from the absence of a preference.
suspend fun switchToOrganization(organizationAccountId: Long?) {
val userId = currentUser?.id ?: return
if (organizationAccountId == null) return appDatabase.organizationsDao.deleteLastSelectionOrganization(userId)
STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt:194
@Upsertonly adds or updates accounts; it never removes accounts omitted from the latest authoritative response. After a membership is revoked, the stale account remains returned byorganizationAccountsForUser()and can keep a stale selection valid, leading the app to route requests to an account the user no longer belongs to. Replace the user's account set transactionally (and clear/fallback an invalid selection) during refresh.
appDatabase.organizationsDao.updateOrganizations(userInfo.organizationAccounts.map { it.toDbModel(userId) })
STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt:155
- The refresh runs after releasing
userSwitchMutex, so a concurrentloadUser/logout can replacecurrentUser. The repository's token callback reads that mutable current user, while the response is persisted under the captureduser.id; this can store user B's organizations under user A and then apply A's default selection to B. Bind the request and writes to the same authenticated user, and discard/cancel the result if that user is no longer current.
launch {
if (user is STUser.AuthUser) refreshUserInfoWithAccountsAndLimits(userId = user.id)
STCommon/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/common/interfaces/upload/UploadSessionRequest.kt:38
- This serializable type is used for persisted iOS upload-session state. A nullable property without a default is still a required key for kotlinx.serialization, so sessions encoded by the previous release will fail to decode after upgrading. Default the new field to
nullto preserve existing personal-upload sessions and source compatibility.
val organizationAccountId: Long?,
STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt:105
- This flow only observes
SelectedOrganizationAccount;organizationAccountsForUser()is a one-shot query insidemap. Updating an account's name, logo, or limits therefore does not invalidate existing collectors, and inserting a previously missing selected account leaves them stuck onnull. Expose a RoomFlow<OrganizationAccount?>backed by a query/join that observes both tables.
This issue also appears in the following locations of the same file:
- line 126
- line 154
- line 194
emitAll(appDatabase.organizationsDao.lastSelectedOrgId(currentUser.id).map { selectedId ->
if (selectedId == null) {
null
} else {
organizationAccountsForUser(currentUser.id).firstOrNull { account -> account.id == selectedId }
Reintroduced by mistake in TransferDao
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
STCommon/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/common/interfaces/upload/UploadSessionRequest.kt:38
- This new field is decoded by
UploadV2Manager.decodeSessionRequest, but it has no default. Any short-term request persisted by an older app version lacks this key, so decoding it after an upgrade throwsMissingFieldExceptionand prevents the upload from resuming. Give the nullable field anulldefault to keep the serialized format backward-compatible.
val organizationAccountId: Long?,
buildXCFramework:50
- A failure in any of these new packaging commands is followed by the next command and final success
echo, so the script can exit with status 0 while the archive is missing or incomplete. Make each packaging failure terminate the script (ideally enable fail-fast for the whole build script).
(cd STDatabase/build/XCFrameworks/release && zip -ry "../../../../build/SwissTransferXCFrameworks.zip" *.xcframework)
(cd STNetwork/build/XCFrameworks/release && zip -ry "../../../../build/SwissTransferXCFrameworks.zip" *.xcframework)
(cd STCore/build/XCFrameworks/release && zip -ry "../../../../build/SwissTransferXCFrameworks.zip" *.xcframework)
STCore/src/commonMain/kotlin/com/infomaniak/multiplatform_swisstransfer/managers/AccountManager.kt:208
updateOrganizationsis only an@Upsert, so accounts omitted by a later API response are never removed. If a user loses an organization, it remains emitted byorganizationAccountsForUser; a saved selection for it also still resolves and can keep uploads targeting an account the user no longer has. Reconcile the user's rows transactionally (delete accounts absent from this response, then upsert), and clear or replace a selection that is no longer present.
appDatabase.organizationsDao.updateOrganizations(userInfo.organizationAccounts.map { it.toDbModel(userId) })
| launch { | ||
| if (user is STUser.AuthUser) refreshUserInfoWithAccountsAndLimits(userId = user.id) | ||
| } |



No description provided.