Skip to content

Allow CRUD operations with database accounts - #2823

Open
ArnyminerZ wants to merge 66 commits into
2820-allow-creating-accounts-into-the-database-servicefrom
2820-allow-creating-accounts-into-the-database-repository
Open

Allow CRUD operations with database accounts#2823
ArnyminerZ wants to merge 66 commits into
2820-allow-creating-accounts-into-the-database-servicefrom
2820-allow-creating-accounts-into-the-database-repository

Conversation

@ArnyminerZ

@ArnyminerZ ArnyminerZ commented Aug 18, 2026

Copy link
Copy Markdown
Member

Organizational

Important

Please make sure an issue or discussion for this change exists and was acknowledged by the
core team before submitting. Uncoordinated pull requests may conflict with the project roadmap
and could be closed without merging — which would be a pity.
See CONTRIBUTING.md for details.

  • An issue or discussion that proposes this change exists and was acknowledged by the core team.
    (The PR is still welcome otherwise, but could be closed at any time without much attention
    when it doesn't fit.)

✨ AI contributions – only check if applicable:

  • This PR and its concept was mainly driven by a human. This does not apply when an issue
    is just referenced/copied into an AI prompt and then the result is submitted as PR.

PRs that are mainly driven by AI often have little worth, as everybody could just copy the issue
into a prompt. The worth of a good PR often comes from the main idea, architectural decisions etc.
behind it. So while all PRs are welcome, such PRs may be reviewed with less human attention and closed
at any time when they don't fit.

Purpose

Allow creating accounts into the database

Short description

  • Added create to AccountRepository, which is suspending, and creates the account into the database, as well as the system.
  • Updated delete so that it also allows deleting db accounts.
  • Updated rename so that it also allows renaming db accounts.
  • Update other operations to support db accounts.

By default, now the accounts are created into the database. When doing so, a copy of the account is created into the system (following the same pattern as we had).

When listing accounts, they are listed both from system, and from database. Since database accounts are also in the system, they are filtered out from the system accounts list.

Checklist

  • The PR has a proper title, description and label.
  • I have self-reviewed the PR.
  • I have added documentation to complex functions and functions that can be used by other modules.
  • I have added reasonable tests or consciously decided to not add tests.

Stack created with GitHub Stacks CLIGive Feedback 💬

@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from 98f256a to 0af97ac Compare August 18, 2026 13:29
@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from 0af97ac to 76f9b26 Compare August 18, 2026 19:47
@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from 216b626 to 36a531f Compare August 18, 2026 20:08
@ArnyminerZ ArnyminerZ changed the title Allow creating accounts into database with createDbAccount Allow CR~U~D operations with database accounts Aug 19, 2026
@ArnyminerZ ArnyminerZ changed the title Allow CR~U~D operations with database accounts Allow CRUD operations with database accounts Aug 19, 2026
@ArnyminerZ ArnyminerZ self-assigned this Aug 19, 2026
@ArnyminerZ
ArnyminerZ requested a lite review from Copilot August 19, 2026 07:17

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

Adds support for “database accounts” (DbAccountId) across account creation, listing, sync tagging, and service lookups so DAVx⁵ can manage accounts stored in the Room database while still maintaining an Android system-account representation.

Changes:

  • Switch account creation to a suspending AccountRepository.create() that creates a DbAccount + initial settings and then creates the corresponding Android account.
  • Extend account enumeration, renaming, and deletion paths to handle both LegacyAccount and DbAccountId.
  • Update sync tagging and service DAO accessors to support DbAccountId-based operations.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
core/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenViewModel.kt Uses the new suspending account creation API during login/setup.
core/src/main/kotlin/at/bitfire/davdroid/sync/worker/BaseSyncWorker.kt Adds DbAccountId support to WorkManager tag generation.
core/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt Adds helper to persist initial settings into the account settings store.
core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt Implements DB-backed account creation and extends delete/rename/list/flow APIs for DbAccountId.
core/src/main/kotlin/at/bitfire/davdroid/db/ServiceDao.kt Enables service lookups by DbAccountId by resolving account name from DB.
core/src/main/kotlin/at/bitfire/davdroid/db/AccountSettingDao.kt Removes an unused blocking query for all settings of an account.
core/src/main/kotlin/at/bitfire/davdroid/accounts/AndroidAccountManager.kt Resolves an Android Account for DbAccountId via DB lookup.
Suppressed comments (2)

core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt:173

  • issue (blocking): Database account rows are left behind when Android account creation fails.

DbAccount (and related AccountSettings via the FK cascade) is inserted before AndroidAccountUtils.createAccount(). If createAccount() returns false (for instance because the system account name already exists), this currently returns null without removing the DB row, which can hide the existing system account from listings (because system accounts are filtered by DB names).

This issue also appears on line 201 of the same file.

        logger.log(Level.INFO, "Creating Android account {0} with initial config {1}", arrayOf(account, userData))

        if (!AndroidAccountUtils.createAccount(context, account, userData, credentials?.password))
            return null

core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt:204

  • issue (blocking): Failed account creation leaves a partially created DB account.

If an InvalidAccountException is thrown while writing the discovered services/settings, the method returns null but keeps the inserted DbAccount + settings in the database. This can break later lookups and also hide the system account in getAll*() due to name filtering.

        } catch (e: InvalidAccountException) {
            logger.log(Level.SEVERE, "Couldn't access account settings", e)
            return null
        }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread core/src/main/kotlin/at/bitfire/davdroid/db/ServiceDao.kt Outdated
Comment thread core/src/main/kotlin/at/bitfire/davdroid/accounts/AndroidAccountManager.kt Outdated
Comment thread core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt Outdated
@ArnyminerZ
ArnyminerZ requested review from cketti and rfc2822 August 19, 2026 07:30
@ArnyminerZ

Copy link
Copy Markdown
Member Author

@rfc2822 I've added you too as a reviewer since this is such an important piece of code. So even though it's not a huge change, its impact is quite high, and design choices have been made, so I believe you can also take a look.

@rfc2822 rfc2822 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Added some things that I noticed.

(Some may need further discussion to clarify what approach we want.)

Comment thread core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt Outdated
Comment thread core/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenViewModel.kt Outdated
private val runningSyncs = Collections.synchronizedSet(HashSet<String>())

/**
* This tag shall be added to every worker that is enqueued by a subclass.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Todo: We should add why this tag should be added – to identify the worker for whoever wants to know whether this sync is already enqueued/running.

"sync-$dataType ${account.type}/${account.name}"
}
is DbAccountId -> TODO("Operation not yet implemented")
is DbAccountId -> "sync-$dataType ${accountId.id}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thought: I identified these current uses:

  • sync worker: prevent multiple concurrent syncs with same tag
  • UI: determine whether a specific sync is enqueued/in progress
  • debug info: dump workers

So these would be affected if an account has a new tag (for instance after the AccountManager → DB migration).

I think none of these will break when the tag is not 1:1 the same after such a migration, but we should keep it in mind.

Comment on lines +42 to +48
// note: this currently depends on the account being created before. at some point, the system accounts
// should be dynamically created, and this method should be able to create the account if it
// doesn't exist yet. for now, we just throw an exception.
// note: currently, this is using runBlocking. ideally we should make the function suspending
runBlocking(ioDispatcher) { dbAccountDao.get(accountId.id) }
?.let { Account(it.name, accountType) }
?: throw NoSuchElementException("No account found for id $accountId")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Issue: I think this method should always return a qualified, existing Android Account (this is what KDoc says).

I'd let this method unimplemented for now and do it in #843.

Comment thread core/src/main/kotlin/at/bitfire/davdroid/accounts/AndroidAccountManager.kt Outdated
Comment on lines +35 to +38
is DbAccountId -> {
val account = dbAccountDao.get(accountId.id) ?: return null
getByAccountAndType(account.name, type)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Issue: Right now (Android account) we have to use account.name as reference because we don't have a a better one. As a consequence, we need to update the service when we for instance rename an account.

However the new AccountId brings us the chance to properly reference accounts by ID. So I think the service table should get a new nullable accountId column that references the DB account, instead of continuing to use the name.

As soon as we have all accounts in the DB (after all migrations are enforced), we can make the accountId non-nullable and then always have a working foreign key.

Comment thread core/src/main/kotlin/at/bitfire/davdroid/db/ServiceDao.kt Outdated
Comment thread core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt Outdated
* @see initialUserData
*/
fun putInitialSettings(credentials: Credentials?, preconfigurationUrl: String?) {
val all = initialUserData(credentials, preconfigurationUrl)

@rfc2822 rfc2822 Aug 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion: The initial user data is now a Map<String,String>, which is not the correct choice, because we want to map the keys to either String or SensitiveString. Thus the confusion that makes the putInitialSettings harder to understand.

I suggest to refactor initialUserData in a separate PR (before this PR) so that it returns a result object, probably of a type like

initialUserData = Map<String,InitialUserSettingValue>

interface InitialUserSettingValue {
  value class StringSetting(value: String)
  class SensitiveStringSetting(value: SensitiveString)
}

or, when we like to keep it simpler, just a

data class InitialUserSettings(
  val values: Map<String,String>,
  val sensitiveValues: Map<String,SensitiveString>
)

Then we can treat the password like any other value and don't have to hardcode KEY_PASSWORD in putInitialSettings.

@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from 0a7035a to 21e134f Compare August 19, 2026 15:11
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Lost when rebasing onto the -service branch: ServiceDao became an
abstract class in this branch's own history, but the accountId-based
query methods (added on the -service branch, written when it was
still an interface) weren't updated to match.
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from 76dcae5 to b1178c9 Compare August 20, 2026 10:24
Allow deleting accounts from database in `delete`
Allow getting account name in `getAccountName` and `getAccountNameBlocking`

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Lost when rebasing onto the -service branch: ServiceDao became an
abstract class in this branch's own history, but the accountId-based
query methods (added on the -service branch, written when it was
still an interface) weren't updated to match.
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
@ArnyminerZ
ArnyminerZ force-pushed the 2820-allow-creating-accounts-into-the-database-repository branch from b1178c9 to 5797f67 Compare August 20, 2026 10:25
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
…o-the-database-repository' into 2820-allow-creating-accounts-into-the-database-repository

# Conflicts:
#	core/src/main/kotlin/at/bitfire/davdroid/repository/AccountRepository.kt
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
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