Skip to content

feat: Add organizations support - #312

Open
LouisCAD wants to merge 31 commits into
mainfrom
add-organization-support
Open

feat: Add organizations support#312
LouisCAD wants to merge 31 commits into
mainfrom
add-organization-support

Conversation

@LouisCAD

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings June 30, 2026 07:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/me request/repository and MyUser model to retrieve organization accounts.
  • Extend Room schema (v2) with organization tables and add organizationAccountId to transfers, wiring org selection into AccountManager/TransferManager.
  • Thread organizationAccountId through 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.

@LouisCAD LouisCAD changed the title feat: Add organization support feat: Add organizations support Jul 6, 2026
@LouisCAD
LouisCAD requested review from Copilot and sirambd July 6, 2026 12:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 10 comments.

LouisCAD and others added 20 commits July 28, 2026 17:09
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 authenticated loadUser, line 196 sees null and replaces the user's choice with defaultOrganizationAccountId, 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

  • @Upsert only adds or updates accounts; it never removes accounts omitted from the latest authoritative response. After a membership is revoked, the stale account remains returned by organizationAccountsForUser() 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 concurrent loadUser/logout can replace currentUser. The repository's token callback reads that mutable current user, while the response is persisted under the captured user.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 null to 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 inside map. Updating an account's name, logo, or limits therefore does not invalidate existing collectors, and inserting a previously missing selected account leaves them stuck on null. Expose a Room Flow<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 }

@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 throws MissingFieldException and prevents the upload from resuming. Give the nullable field a null default 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

  • updateOrganizations is only an @Upsert, so accounts omitted by a later API response are never removed. If a user loses an organization, it remains emitted by organizationAccountsForUser; 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) })

Comment on lines +168 to +170
launch {
if (user is STUser.AuthUser) refreshUserInfoWithAccountsAndLimits(userId = user.id)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants