fix(server,flutter): sync wire format + systemVariant column (#357)
Two fixes in one commit because they're entangled — the systemVariant work would have been theater otherwise. ## The wire-format bug /api/library/sync was emitting PascalCase JSON for artist / album / track / playlist upserts (raw json.Marshal of sqlc-generated structs with no JSON tags — sqlc.yaml: emit_json_tags=false). Flutter's sync_controller _*FromJson reads snake_case keys, so all metadata sync rows landed in drift with empty strings / zero ints. The like_track / like_album / like_artist / playlist_track entities work because they're hand-built `map[string]string` payloads with snake_case keys — they sidestepped the bug. The 4 raw-marshal entities did not. Existing sync test caught zero of this — it asserts on len(upserts) not field shape. Fix: server-side view structs in library_sync_views.go with proper JSON tags + pgtype-flattening (UUID → 8-4-4-4-12 hex string, Date → "2006-01-02"). Mirrors the playlistRowView pattern from /api/playlists. New library_sync_views_test.go pins the wire keys so future field-name drift breaks loud. ## systemVariant column (closes #357 plan C v1 limitation) playlistSyncView now carries `system_variant` server → wire. Flutter drift schema bumped from 1 → 2 with onUpgrade adding the `systemVariant TEXT NULL` column to cached_playlists. Cursor reset to 0 in the migration so existing rows refresh with the new field on the next sync. playlistsListProvider now filters locally by systemVariant: - kind='user' → systemVariant IS NULL (the add-to-playlist sheet's intent) - kind='system' → systemVariant IS NOT NULL - kind='all' → no filter Closes the documented v1 limitation where the add-to-playlist sheet showed system playlists alongside user-created ones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,12 +17,15 @@ final playlistsApiProvider = FutureProvider<PlaylistsApi>((ref) async {
|
||||
return PlaylistsApi(await ref.watch(dioProvider.future));
|
||||
});
|
||||
|
||||
/// Drift-first per #357 plan C. Returns all cached playlists for the
|
||||
/// current user (owned) + public playlists from others. The `kind`
|
||||
/// family arg only affects the REST cold-cache fetch shape — drift
|
||||
/// can't distinguish 'user' from 'system' kind because systemVariant
|
||||
/// isn't persisted (TODO: add column + schema bump in a follow-up).
|
||||
/// For v1 this means add-to-playlist sheet shows system playlists too.
|
||||
/// Drift-first per #357 plan C. Returns cached playlists filtered to
|
||||
/// match the `kind` family arg:
|
||||
/// - 'user' → only user-created (systemVariant null), owned by current user
|
||||
/// - 'system' → only system-generated (systemVariant non-null), owned
|
||||
/// - 'all' → everything: own user playlists + own system + others' public
|
||||
///
|
||||
/// systemVariant column added in drift schema v2 (#357 follow-up); previous
|
||||
/// behavior leaked system playlists into add-to-playlist sheet because we
|
||||
/// couldn't filter locally.
|
||||
final playlistsListProvider =
|
||||
StreamProvider.family<PlaylistsList, String>((ref, kind) {
|
||||
final db = ref.watch(appDbProvider);
|
||||
@@ -42,11 +45,16 @@ final playlistsListProvider =
|
||||
},
|
||||
toResult: (rows) {
|
||||
if (user == null) return PlaylistsList.empty();
|
||||
final owned = rows
|
||||
final filtered = rows.where((r) {
|
||||
if (kind == 'user') return r.systemVariant == null;
|
||||
if (kind == 'system') return r.systemVariant != null;
|
||||
return true; // 'all'
|
||||
});
|
||||
final owned = filtered
|
||||
.where((r) => r.userId == user.id)
|
||||
.map((r) => r.toRef())
|
||||
.toList();
|
||||
final pub = rows
|
||||
final pub = filtered
|
||||
.where((r) => r.userId != user.id && r.isPublic)
|
||||
.map((r) => r.toRef())
|
||||
.toList();
|
||||
|
||||
Reference in New Issue
Block a user