diff --git a/web/src/at_record_web/effects.gleam b/web/src/at_record_web/effects.gleam index 546a3a9..677e3e2 100644 --- a/web/src/at_record_web/effects.gleam +++ b/web/src/at_record_web/effects.gleam @@ -12,11 +12,9 @@ import at_record/gen/shelf/list_entries import at_record_web/appview import at_record_web/browser import at_record_web/model.{ - type AmendDraft, type DiscogsResult, type Display, type EditProposal, - type Form, type ImportRun, type NetworkMatch, type ReleaseInfo, - type Suggestion, type Theme, DiscogsResult, DiscogsSearchPage, EditProposal, - HandleSuggestion, ImportRun, NetworkMatch, ProposalFields, ReleaseInfo, - Suggestion, + type AmendDraft, type Display, type EditProposal, type Form, type ReleaseInfo, + type Theme, DiscogsSearchPage, EditProposal, HandleSuggestion, ProposalFields, + ReleaseInfo, } import at_record_web/money import at_record_web/msg.{ @@ -173,7 +171,10 @@ pub fn discogs_search( artist: Option(String), ) -> Effect(Msg) { let decoder = { - use results <- decode.field("results", decode.list(discogs_decoder())) + use results <- decode.field( + "results", + decode.list(discogs_search_releases.discogs_release_decoder()), + ) use page <- decode.field("page", decode.int) use pages <- decode.field("pages", decode.int) decode.success(DiscogsSearchPage(results:, page:, pages:)) @@ -186,7 +187,11 @@ pub fn discogs_search( pub fn discogs_search_artists(query: String, page: Int) -> Effect(Msg) { let decoder = - decode.field("results", decode.list(artist_decoder()), decode.success) + decode.field( + "results", + decode.list(discogs_search_artists.artist_decoder()), + decode.success, + ) let url = xrpc("discogs.searchArtists", [ #("q", query), @@ -195,16 +200,6 @@ pub fn discogs_search_artists(query: String, page: Int) -> Effect(Msg) { rsvp.get(url, rsvp.expect_json(decoder, GotArtists)) } -fn artist_decoder() -> decode.Decoder(model.ArtistHit) { - decode.map(discogs_search_artists.artist_decoder(), fn(artist) { - model.ArtistHit( - id: artist.id, - name: artist.name, - thumb_url: artist.thumb_url, - ) - }) -} - pub fn handle_search(query: String) -> Effect(Msg) { let decoder = { use did <- decode.field("did", decode.string) @@ -274,17 +269,17 @@ pub fn scan_lookup(barcode: String) -> Effect(Msg) { use result <- decode.optional_field( "result", None, - decode.optional(discogs_decoder()), + decode.optional(discogs_search_releases.discogs_release_decoder()), ) use suggestions <- decode.optional_field( "suggestions", [], - decode.list(suggestion_decoder()), + decode.list(discogs_resolve_barcode.suggestion_decoder()), ) use network <- decode.optional_field( "network", None, - decode.optional(network_decoder()), + decode.optional(discogs_resolve_barcode.network_match_decoder()), ) decode.success(ScanLookup(result:, suggestions:, network:)) } @@ -292,25 +287,6 @@ pub fn scan_lookup(barcode: String) -> Effect(Msg) { rsvp.get(url, rsvp.expect_json(decoder, GotScanResult(barcode, _))) } -fn suggestion_decoder() -> decode.Decoder(Suggestion) { - use release <- decode.field("release", discogs_decoder()) - use confidence <- decode.field("confidence", decode.int) - decode.success(Suggestion(release:, confidence:)) -} - -fn network_decoder() -> decode.Decoder(NetworkMatch) { - decode.map(discogs_resolve_barcode.network_match_decoder(), fn(match) { - NetworkMatch( - uri: match.uri, - cid: match.cid, - title: match.title, - artist_display: match.artist_display, - thumb_url: match.thumb_url, - publisher_handle: match.publisher_handle, - ) - }) -} - /// The discogs ids already in the crate, fetched once per scan session /// (not once per scan) so a shot-tray session doesn't refetch the whole /// event log for every barcode. @@ -324,20 +300,6 @@ pub fn scan_seen() -> Effect(Msg) { ) } -fn discogs_decoder() -> decode.Decoder(DiscogsResult) { - decode.map(discogs_search_releases.discogs_release_decoder(), fn(release) { - DiscogsResult( - discogs_id: release.discogs_id, - title: release.title, - artist: release.artist, - year: release.year, - format: release.format, - thumb_url: release.thumb_url, - cover_url: release.cover_url, - ) - }) -} - /// Whether (and as whom) the user's Discogs account is connected. pub fn discogs_status() -> Effect(Msg) { let decoder = { @@ -372,24 +334,15 @@ pub fn discogs_disconnect() -> Effect(Msg) { ) } -fn import_run_decoder() -> decode.Decoder(ImportRun) { - decode.map(discogs_import_collection.import_run_decoder(), fn(run) { - ImportRun( - imported: run.imported, - updated: run.updated, - skipped: run.skipped, - done: run.done, - note: run.note, - ) - }) -} - /// One capped import run; the server response says whether to run again. pub fn discogs_import() -> Effect(Msg) { rsvp.post( xrpc("discogs.importCollection", []), json.object([]), - rsvp.expect_json(import_run_decoder(), GotDiscogsImport), + rsvp.expect_json( + discogs_import_collection.import_run_decoder(), + GotDiscogsImport, + ), ) } @@ -399,7 +352,10 @@ pub fn discogs_import_wantlist() -> Effect(Msg) { rsvp.post( xrpc("discogs.importWantlist", []), json.object([]), - rsvp.expect_json(import_run_decoder(), GotDiscogsImport), + rsvp.expect_json( + discogs_import_collection.import_run_decoder(), + GotDiscogsImport, + ), ) } diff --git a/web/src/at_record_web/model.gleam b/web/src/at_record_web/model.gleam index 25cd32b..b4f5b6c 100644 --- a/web/src/at_record_web/model.gleam +++ b/web/src/at_record_web/model.gleam @@ -1,6 +1,10 @@ import at_record/gen/catalog/list_releases import at_record/gen/catalog/release as catalog_release import at_record/gen/defs.{type Snapshot} +import at_record/gen/discogs/import_collection as discogs_import_collection +import at_record/gen/discogs/resolve_barcode as discogs_resolve_barcode +import at_record/gen/discogs/search_artists as discogs_search_artists +import at_record/gen/discogs/search_releases as discogs_search_releases import at_record/gen/feed/get_feed_skeleton.{type FeedItem} import at_record/gen/shelf/entry.{type ShelfEntry} import at_record/gen/shelf/list_entries @@ -371,18 +375,11 @@ pub type HandleSuggestion { ) } -/// A Discogs search hit, normalized by the server for the add form. -pub type DiscogsResult { - DiscogsResult( - discogs_id: Int, - title: String, - artist: String, - year: Option(Int), - format: Option(String), - thumb_url: Option(String), - cover_url: Option(String), - ) -} +/// A Discogs search hit, normalized by the server for the add form: the +/// `discogs.searchReleases` row, generated from the lexicon and adopted +/// directly as the web model. +pub type DiscogsResult = + discogs_search_releases.DiscogsRelease /// One page of Discogs search results plus its position in the full set. pub type DiscogsSearchPage { @@ -390,10 +387,11 @@ pub type DiscogsSearchPage { } /// A Discogs artist search hit for the artist-scoped autocomplete; `id` is -/// the raw Discogs artist id, carried for later catalog.artist linking. -pub type ArtistHit { - ArtistHit(id: String, name: String, thumb_url: Option(String)) -} +/// the raw Discogs artist id, carried for later catalog.artist linking. The +/// `discogs.searchArtists` row, generated from the lexicon and adopted +/// directly as the web model. +pub type ArtistHit = + discogs_search_artists.Artist /// Display-relevant fields off the entry's resolved catalog release. pub type ReleaseInfo { @@ -407,24 +405,18 @@ pub type ReleaseInfo { } /// A "did you mean" candidate for a barcode that resolved to nothing, with a -/// 0-100 confidence the server scored it at. -pub type Suggestion { - Suggestion(release: DiscogsResult, confidence: Int) -} +/// 0-100 confidence the server scored it at. The `discogs.resolveBarcode` +/// suggestion row, generated from the lexicon and adopted directly as the +/// web model. +pub type Suggestion = + discogs_resolve_barcode.Suggestion /// A release already known to the shared at-record catalog, found before any /// Discogs lookup was made; `publisher_handle` is best-effort, mirroring how -/// `BrowseRelease` carries its own. -pub type NetworkMatch { - NetworkMatch( - uri: String, - cid: String, - title: String, - artist_display: Option(String), - thumb_url: Option(String), - publisher_handle: Option(String), - ) -} +/// `BrowseRelease` carries its own. The `discogs.resolveBarcode` network +/// match, generated from the lexicon and adopted directly as the web model. +pub type NetworkMatch = + discogs_resolve_barcode.NetworkMatch /// A scan row's add-eligible item: either a Discogs match (added via the /// manual add path) or a network match (added via `adoptRelease`, same as @@ -684,16 +676,11 @@ pub fn add_ignored_proposal( } } -/// The outcome of one capped collection-import run. -pub type ImportRun { - ImportRun( - imported: Int, - updated: Int, - skipped: Int, - done: Bool, - note: Option(String), - ) -} +/// The outcome of one capped collection-import run: the +/// `discogs.importCollection`/`discogs.importWantlist` response, generated +/// from the lexicon and adopted directly as the web model. +pub type ImportRun = + discogs_import_collection.ImportRun pub type Model { Model( diff --git a/web/test/add_test.gleam b/web/test/add_test.gleam index def4496..7d07c4f 100644 --- a/web/test/add_test.gleam +++ b/web/test/add_test.gleam @@ -1,6 +1,6 @@ +import at_record/gen/discogs/search_artists import at_record_web/model.{ - ArtistHit, Discogs, Failure, Form, Model, Notice, Success, blank_discogs, - blank_form, + Discogs, Failure, Form, Model, Notice, Success, blank_discogs, blank_form, } import at_record_web/msg.{ FormArtist, FormCounterparty, FormFolder, FormPriceAmount, FormPriceCurrency, @@ -113,14 +113,14 @@ pub fn form_artist_debounces_and_clears_id_test() { } pub fn use_artist_sets_name_and_id_test() { - let hit = ArtistHit(id: "42", name: "Slint", thumb_url: None) + let hit = search_artists.Artist(id: "42", name: "Slint", thumb_url: None) let #(model, _effect) = update(base(), UseArtist(hit)) assert model.form.artist == "Slint" assert model.discogs.artist_id == Some("42") } pub fn got_artists_populates_suggestions_test() { - let hit = ArtistHit(id: "42", name: "Slint", thumb_url: None) + let hit = search_artists.Artist(id: "42", name: "Slint", thumb_url: None) let #(model, _effect) = update(base(), GotArtists(Ok([hit]))) assert model.discogs.artist_results == [hit] } diff --git a/web/test/scan_review_test.gleam b/web/test/scan_review_test.gleam index 28ffc7f..816014f 100644 --- a/web/test/scan_review_test.gleam +++ b/web/test/scan_review_test.gleam @@ -1,6 +1,7 @@ +import at_record/gen/discogs/resolve_barcode import at_record_web/model.{ - type NetworkMatch, type ScanRow, Matched, Model, NetworkMatch, NetworkMatched, - NoMatch, ScanRow, ScanState, blank_scan, scan_importable, + type NetworkMatch, type ScanRow, Matched, Model, NetworkMatched, NoMatch, + ScanRow, ScanState, blank_scan, scan_importable, } import at_record_web/pages/scan_review import gleam/option.{None, Some} @@ -9,7 +10,7 @@ import lustre/element import support.{discogs_result, logged_in} fn a_network_match() -> NetworkMatch { - NetworkMatch( + resolve_barcode.NetworkMatch( uri: "at://did:plc:pub/dev.mokkenstorm.crate.catalog.release/r1", cid: "bafyrel", title: "Spiderland", @@ -98,9 +99,9 @@ fn a_no_match_with_suggestions() -> List(ScanRow) { ScanRow( barcode: "b4", outcome: NoMatch([ - model.Suggestion(release: discogs_result(9), confidence: 62), - model.Suggestion(release: discogs_result(3), confidence: 41), - model.Suggestion(release: discogs_result(7), confidence: 12), + resolve_barcode.Suggestion(release: discogs_result(9), confidence: 62), + resolve_barcode.Suggestion(release: discogs_result(3), confidence: 41), + resolve_barcode.Suggestion(release: discogs_result(7), confidence: 12), ]), ), ] diff --git a/web/test/scan_test.gleam b/web/test/scan_test.gleam index 9a731ed..a8dd51e 100644 --- a/web/test/scan_test.gleam +++ b/web/test/scan_test.gleam @@ -1,10 +1,10 @@ import at_record/gen/defs.{Snapshot} +import at_record/gen/discogs/resolve_barcode import at_record/gen/shelf/list_entries import at_record_web/model.{ type Model, type NetworkMatch, Adding, Crate, Failed, FromDiscogs, FromNetwork, - Matched, Model, NetworkMatch, NetworkMatched, NoMatch, Own, OwnCrate, Pending, - Scan, ScanDone, ScanReview, ScanRow, ScanState, ShelfLoaded, Suggestion, - blank_scan, set_crate, + Matched, Model, NetworkMatched, NoMatch, Own, OwnCrate, Pending, Scan, + ScanDone, ScanReview, ScanRow, ScanState, ShelfLoaded, blank_scan, set_crate, } import at_record_web/msg.{ AcceptSuggestion, BarcodeDetected, BatchItemDone, GotScanResult, GotScanSeen, @@ -150,7 +150,9 @@ pub fn got_scan_result_no_match_carries_suggestions_test() { ScanRow(barcode: "b1", outcome: Pending), ]), ) - let suggestions = [Suggestion(release: discogs_result(9), confidence: 62)] + let suggestions = [ + resolve_barcode.Suggestion(release: discogs_result(9), confidence: 62), + ] let #(model, _) = update( seeded, @@ -168,7 +170,10 @@ pub fn scan_page_renders_no_match_row_without_did_you_mean_test() { ScanRow( barcode: "825646350919", outcome: NoMatch([ - Suggestion(release: discogs_result(9), confidence: 62), + resolve_barcode.Suggestion( + release: discogs_result(9), + confidence: 62, + ), ]), ), ]), @@ -258,7 +263,7 @@ pub fn got_scan_seen_stores_ids_test() { } fn a_network_match() -> NetworkMatch { - NetworkMatch( + resolve_barcode.NetworkMatch( uri: "at://did:plc:pub/dev.mokkenstorm.crate.catalog.release/r1", cid: "bafyrel", title: "Spiderland", @@ -297,7 +302,9 @@ pub fn accept_suggestion_promotes_the_row_to_matched_without_a_write_test() { scan: ScanState(..blank_scan(), rows: [ ScanRow( barcode: "b1", - outcome: NoMatch([Suggestion(release:, confidence: 62)]), + outcome: NoMatch([ + resolve_barcode.Suggestion(release:, confidence: 62), + ]), ), ]), ) @@ -315,7 +322,9 @@ pub fn accept_suggestion_grows_the_importable_count_test() { scan: ScanState(..blank_scan(), rows: [ ScanRow( barcode: "b1", - outcome: NoMatch([Suggestion(release:, confidence: 62)]), + outcome: NoMatch([ + resolve_barcode.Suggestion(release:, confidence: 62), + ]), ), ]), ) @@ -347,7 +356,9 @@ pub fn accept_suggestion_still_promotes_while_a_run_is_active_test() { scan: ScanState(..blank_scan(), importing: True, rows: [ ScanRow( barcode: "b1", - outcome: NoMatch([Suggestion(release:, confidence: 62)]), + outcome: NoMatch([ + resolve_barcode.Suggestion(release:, confidence: 62), + ]), ), ]), ) diff --git a/web/test/settings_test.gleam b/web/test/settings_test.gleam index 004a5b1..dd0f94a 100644 --- a/web/test/settings_test.gleam +++ b/web/test/settings_test.gleam @@ -1,6 +1,6 @@ +import at_record/gen/discogs/import_collection import at_record_web/model.{ - Dark, Discogs, Failure, ImportRun, Model, Notice, Settings, Success, - blank_discogs, + Dark, Discogs, Failure, Model, Notice, Settings, Success, blank_discogs, } import at_record_web/msg.{ ArmLogout, DisarmLogout, DiscogsImportWantlist, GotDiscogsImport, @@ -40,7 +40,7 @@ pub fn import_result_summarises_partial_run_test() { update( importing, GotDiscogsImport( - Ok(ImportRun( + Ok(import_collection.ImportRun( imported: 100, updated: 0, skipped: 18, @@ -62,7 +62,7 @@ pub fn import_result_complete_run_has_no_continue_hint_test() { update( logged_in(), GotDiscogsImport( - Ok(ImportRun( + Ok(import_collection.ImportRun( imported: 3, updated: 2, skipped: 0, diff --git a/web/test/support.gleam b/web/test/support.gleam index 41d5c75..e65d56c 100644 --- a/web/test/support.gleam +++ b/web/test/support.gleam @@ -2,11 +2,12 @@ //// suite's per-page test modules. import at_record/gen/defs.{type Snapshot, Price, Snapshot} +import at_record/gen/discogs/search_releases import at_record/gen/shelf/list_entries import at_record_web/model.{ - type DiscogsResult, type Display, type Entry, type Model, Crate, DiscogsResult, - Grid, LoggedIn, LoggedOut, Model, Own, OwnCrate, ShelfLoaded, ShelfLoading, - System, blank_amend, blank_discogs, blank_form, blank_scan, set_crate, + type DiscogsResult, type Display, type Entry, type Model, Crate, Grid, + LoggedIn, LoggedOut, Model, Own, OwnCrate, ShelfLoaded, ShelfLoading, System, + blank_amend, blank_discogs, blank_form, blank_scan, set_crate, } import at_record_web/pages/crate import gleam/dict @@ -137,7 +138,7 @@ pub fn a_detail() -> model.EntryDetail { } pub fn discogs_result(id: Int) -> DiscogsResult { - DiscogsResult( + search_releases.DiscogsRelease( discogs_id: id, title: "Spiderland", artist: "Slint",