refactor(profiles): remove dead registry surface

Delete unused registry members (ProfileConnectionRegistry.insertIfAbsent /
removeAllForProfile, ProfileRegistry.reorder, ProfilesView.countFor,
ConnectionRegistry.getDefault) and their orphan tests, refreshing the stale
docs that named removeAllForProfile as the profile-delete cleanup path.

Repair the "one default per profile" join-row invariant: the connectionId FK
cascade (foreign_keys=ON) silently drops a profile's default row while its
other rows survive, leaving it defaultless. Add promoteMissingDefaults, share
a deterministic re-promotion helper with remove(), and re-promote in
removeAllForConnection.
This commit is contained in:
edde746
2026-07-02 11:41:25 +02:00
parent d193633a5d
commit 1059658515
8 changed files with 84 additions and 70 deletions
+31 -22
View File
@@ -118,13 +118,6 @@ class ProfileConnectionRegistry {
);
}
/// Insert a new join row only if `(profileId, connectionId)` doesn't
/// already exist. Used by [ProfileSyncService] to surface new Plex Home
/// users without clobbering tokens cached by prior switches.
Future<void> insertIfAbsent(ProfileConnection pc) async {
await _db.into(_db.profileConnections).insert(await _companion(pc), mode: InsertMode.insertOrIgnore);
}
/// Cache the freshly-acquired user token (e.g. after a `/home/users/switch`
/// call). Updates `tokenAcquiredAt` to now.
Future<void> recordToken(String profileId, String connectionId, String token) async {
@@ -149,12 +142,30 @@ class ProfileConnectionRegistry {
await (_db.delete(
_db.profileConnections,
)..where((t) => t.profileId.equals(profileId) & t.connectionId.equals(connectionId))).go();
// If we just removed the default, promote the oldest remaining row.
final remaining = await (_db.select(_db.profileConnections)..where((t) => t.profileId.equals(profileId))).get();
if (remaining.isNotEmpty && !remaining.any((r) => r.isDefault)) {
await (_db.update(_db.profileConnections)
..where((t) => t.profileId.equals(profileId) & t.connectionId.equals(remaining.first.connectionId)))
.write(const ProfileConnectionsCompanion(isDefault: Value(true)));
await _promoteDefaultIfMissing(profileId);
}
/// Re-promote a default for [profileId] when it has join rows but none is
/// flagged default — removing the default row would otherwise leave the
/// profile defaultless. Deterministic: picks the lowest connectionId,
/// matching [listForProfile]'s secondary ordering.
Future<void> _promoteDefaultIfMissing(String profileId) async {
final rows = await (_db.select(_db.profileConnections)..where((t) => t.profileId.equals(profileId))).get();
if (rows.isEmpty || rows.any((r) => r.isDefault)) return;
final pick = rows.map((r) => r.connectionId).reduce((a, b) => a.compareTo(b) <= 0 ? a : b);
await (_db.update(_db.profileConnections)
..where((t) => t.profileId.equals(profileId) & t.connectionId.equals(pick)))
.write(const ProfileConnectionsCompanion(isDefault: Value(true)));
}
/// Repair the "exactly one default per profile" invariant across every
/// profile. The `connectionId` FK cascade (PRAGMA foreign_keys=ON) deletes
/// join rows silently when a Connection is removed, so a profile can be left
/// with surviving rows but no default flag.
Future<void> promoteMissingDefaults() async {
final profileIds = (await _db.select(_db.profileConnections).get()).map((r) => r.profileId).toSet();
for (final profileId in profileIds) {
await _promoteDefaultIfMissing(profileId);
}
}
@@ -172,16 +183,14 @@ class ProfileConnectionRegistry {
}
/// Remove every join row referencing [connectionId] (e.g. when a Connection
/// is deleted). Drift's referential integrity isn't enabled by default for
/// SQLite without `PRAGMA foreign_keys=ON`, so we cascade explicitly.
/// is deleted). The `connectionId` FK (PRAGMA foreign_keys=ON) already
/// cascades these rows away when the Connection row itself is deleted; this
/// stays the explicit path for callers that drop the rows first, and either
/// way repairs any profile the removal left without a default.
Future<int> removeAllForConnection(String connectionId) async {
return await (_db.delete(_db.profileConnections)..where((t) => t.connectionId.equals(connectionId))).go();
}
/// Wipe every join row for [profileId] (e.g. when a Plex Home profile's
/// parent connection is removed).
Future<int> removeAllForProfile(String profileId) async {
return await (_db.delete(_db.profileConnections)..where((t) => t.profileId.equals(profileId))).go();
final removed = await (_db.delete(_db.profileConnections)..where((t) => t.connectionId.equals(connectionId))).go();
await promoteMissingDefaults();
return removed;
}
/// Wipe the entire join table. Used by sign-out so a fresh sign-in starts