mirror of
https://github.com/yattee/yattee.git
synced 2026-10-24 21:52:51 +00:00
An account whose source is gone was listed nowhere: `orphanedAccounts` had no readers, and the credential behind it could not be reached at all — `AppEnvironment.accountCredentialsManager(for:)` resolves the store from the account's instance, and an orphan has none. The new accounts screen lists them and removes them by account ID, asking every account-keyed store because the backend is no longer knowable. The pre-revamp instance-keyed item is deliberately left behind: identifying it needs the instance too. Deleting a source has always signed out and removed the accounts on it. Both confirmations now say so, and name them. The sign-in that files a credential against an account is now one implementation instead of a private method on the source editor, so the accounts screen and the new optional sign-in step after adding a source attach a login exactly as the editor does. Extracted verbatim — the identity-scope guard, the account re-resolve, the write-then-read-back and the rollback are what keep a half-written login from looking real. Signing out from the per-instance section now asks first, and both sign-out paths run one cascade rather than two copies of it.
43 lines
1.5 KiB
Swift
43 lines
1.5 KiB
Swift
//
|
|
// AccountRemoval.swift
|
|
// Yattee
|
|
//
|
|
// Taking an account off this device by hand.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// The removal cascade for an account whose instance is gone.
|
|
///
|
|
/// Kept out of `AppEnvironment` so the ordering can be exercised directly: an
|
|
/// orphan is exactly the case where the usual lookups return nothing, and a
|
|
/// cascade that quietly skips a step leaves a secret behind with no screen left
|
|
/// to reach it from.
|
|
@MainActor
|
|
enum AccountRemoval {
|
|
/// Removes an orphaned account and the credential filed under its ID.
|
|
///
|
|
/// `AppEnvironment.accountCredentialsManager(for:)` resolves the store from
|
|
/// the account's instance, and an orphan has none — going through it would
|
|
/// return `nil` and delete nothing. Every account-keyed store is asked to
|
|
/// drop the ID instead; at most one of them holds anything, and the scoped
|
|
/// key needs only the ID. The unscoped legacy item is deliberately left
|
|
/// alone: identifying it needs the instance, which is precisely what is
|
|
/// missing.
|
|
static func removeOrphan(
|
|
_ accountID: UUID,
|
|
stores: [any AccountCredentialsManager],
|
|
accountsManager: AccountsManager,
|
|
discardData: (UUID) -> Void
|
|
) {
|
|
for store in stores {
|
|
store.deleteCredential(forAccountID: accountID)
|
|
}
|
|
|
|
accountsManager.removeAccount(accountID)
|
|
// Whatever the account had in memory goes with it, or its subscriptions
|
|
// and playlists would keep answering for an account nobody has.
|
|
discardData(accountID)
|
|
}
|
|
}
|