Commit Graph

549 Commits

Author SHA1 Message Date
Claudio Ortolina cccd28a9f9 ML-165: implementation 2026-05-05 13:43:18 +01:00
Claudio Ortolina ba2566828c Fix FTS5 crashes when searching with special characters
Always wrap FTS5 query terms in phrase double-quotes instead of
checking a hardcoded list of special characters. Characters like
|, ;, +, #, @ were missing from the list, causing FTS5 syntax
errors (production errors #1 and #1398). Phrase-quoting every term
produces identical results for normal ASCII search (verified via
SQL) while safely handling any character FTS5 chokes on.

Add regression tests for bare special characters and special
characters mixed with normal words.
2026-05-05 12:05:12 +01:00
Claudio Ortolina 7c6760d7db ML-158: force single-line log output in production
Three-layer architecture:
- Logster v2 handles HTTP request and LiveView socket telemetry in logfmt format
- Custom formatter as safety net escaping any remaining embedded newlines
- Config flag (single_line_logging) gating Logster attachment to prod only

Dev environment retains multi-line format for readability.
2026-05-04 15:01:10 +01:00
Claudio Ortolina 1ddef07bcc fix(api): handle non-integer error ids, clamp negative pagination params
- show/2: use case instead of bare Integer.parse match so
  /api/v1/errors/not-an-id returns 404 instead of crashing
- index/2: clamp limit to >= 1 and offset to >= 0 to prevent
  SQLite from treating negative values as unlimited
- Add 3 tests: non-integer id 404, negative limit/offset clamping
2026-05-04 14:17:45 +01:00
Claudio Ortolina 93ac86f748 fix(errors): LIKE wildcard escaping, memory-efficient counts, context tests
- Escape LIKE wildcards (% and _) in search queries using fragment ESCAPE
  to prevent accidental pattern expansion and potential DoS vectors
- Use Repo.aggregate for occurrence_count instead of loading all
  occurrences into memory in get_error/1
- Use Repo.aggregate(:min, :inserted_at) for first_occurrence_at instead
  of fragile List.last/1 that depends on query ordering
- Add MusicLibrary.ErrorsTest with 15 tests covering list_errors/1
  (default, status/muted/search filters, wildcard escaping, pagination,
  empty results) and get_error/1 (with/without occurrences, not_found)
2026-05-04 14:11:46 +01:00
Claudio Ortolina 51bd24d5b4 ML-162: enable /api/v1/errors endpoint 2026-05-04 13:14:40 +01:00
Claudio Ortolina 84012cbd1c Update to error_tracker 0.9
Includes amendments resulting from new events, and proper handling of
the muted attribute
2026-05-03 14:46:40 +01:00
Claudio Ortolina 0b78f24fed ML-155: Compact collection summary and add stats preamble 2026-05-02 07:11:18 +01:00
Claudio Ortolina ca5b13ad8d Remove some uses of List.last 2026-05-01 17:03:42 +01:00
Claudio Ortolina 3b558044b4 refactor: extract Records sub-contexts (Search, Import, Enrichment)
Split the 450+ line Records context into focused modules:

- Records.Search — FTS5 search, SearchParser integration, genre listing
- Records.Import — MusicBrainz release/group import, release status
- Records.Enrichment — genre population, cover refresh, color extraction,
  MusicBrainz data refresh
- Records.Query — shared helpers (essential_fields, order_alphabetically macro)

Records module is now a facade with defdelegate for compile-time safety.
CRUD and PubSub stay directly in Records. Tests moved alongside each
sub-module. Zero callers changed, 886 tests pass.
2026-04-30 17:20:37 +01:00
Claudio Ortolina 3849b338f3 Improve performance of job counting in maintenance
- Use a single query for all workers
- Replace the manual Ecto query against Oban.Job with Oban.Job.query/1
2026-04-30 14:54:56 +01:00
Claudio Ortolina c489fde1cc ML-152: add /api/v1/ version prefix to all API routes 2026-04-30 11:57:43 +01:00
Claudio Ortolina f2839dfaf1 Test needs to be sync 2026-04-29 22:50:29 +01:00
Claudio Ortolina 9f8ef6c329 Use larger image for Last.fm artists 2026-04-25 21:22:18 +01:00
Claudio Ortolina 9113df5879 ML-146: honour API retry headers 2026-04-25 07:39:49 +01:00
Claudio Ortolina d588e5738f Remove redundant tests 2026-04-25 06:57:03 +01:00
Claudio Ortolina a1c665b490 ML-21: Classify API errors as transient vs permanent 2026-04-24 14:02:15 +01:00
Claudio Ortolina 252caf31ce ML-11: Improve performance of Telemetry.Storage with in-memory buffer 2026-04-24 10:30:28 +01:00
Claudio Ortolina 0784fc5e0c Fix page title for scrobble anything release page 2026-04-23 21:06:32 +01:00
Claudio Ortolina 1bd5707cb4 Fix page title for scrobble anything index and release group route 2026-04-23 21:06:12 +01:00
Claudio Ortolina 8911afad94 Add tests for Top Albums component 2026-04-23 16:41:44 +01:00
Claudio Ortolina c7c7e1fe4e Navigate from scrobble search to release-group page 2026-04-23 14:47:50 +01:00
Claudio Ortolina 0dcd6eab83 Replace /scrobble/:release_id with nested route 2026-04-23 14:39:26 +01:00
Claudio Ortolina d8041dc0dd Add /scrobble/:rg_id release-group page 2026-04-23 14:35:58 +01:00
Claudio Ortolina 4c48bda7b2 Refactor Release component to take release_id 2026-04-23 14:19:11 +01:00
Claudio Ortolina 0d1f580379 Refactor TracklistPdf to take release alone 2026-04-23 07:48:53 +01:00
Claudio Ortolina f7f38d580e Enable unaliased modules ex_slop check 2026-04-22 20:37:09 +01:00
Claudio Ortolina 0776700170 ML-142: Improve scrobble UI in the release component
- Sticky bar for easier track-based scrobbling
- Support setting custom finish time
2026-04-22 17:19:35 +01:00
Claudio Ortolina 9851a5c6bf Use cart with barcode scanner 2026-04-20 15:46:40 +01:00
Claudio Ortolina eaece07193 Count formats in results 2026-04-20 15:15:29 +01:00
Claudio Ortolina c4ebe636ff Fix ML-143 review: security, worker, and test improvements
Closes ML-143 review findings:
- cast_id/1 uses Integer.parse instead of String.to_integer
- add_to_cart validates against server-side release_groups
- JS.push payload trimmed to {id, format} only
- Oban.insert_all result checked for partial failures
- handle_async success resets @importing? to false
- ImportFromMusicbrainzReleaseGroup gets unique constraint
- Record.parse_datetime/1 made public with safe match
- Workers delegate to Record.parse_datetime/1
- Test coverage: change_format, clear_cart, wishlist single import
2026-04-20 15:15:29 +01:00
Claudio Ortolina 3f5ce060b1 Implement ML-143: Cart-style multi record import
Closes ML-143.
2026-04-20 15:15:29 +01:00
Claudio Ortolina 78487b0085 Pin Wikipedia intro_html to fixture value
Map.has_key? verified the key existed but not the content. The
fixture is deterministic — assert the stored value equals
Wikipedia.Fixtures.article_extract_html/0.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina a6319d55df Pin artist_live/show image_data_hash assertions
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 07c99992a3 Pin barcode_scan release assertions
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 162c10e70a Pin fetch_artist_image asset lookup
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bce8560ce8 Pin fetch_artist_info stored-data assertions
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 1f457f0f11 Pin prune_assets test to %Asset{hash: ^hash} match
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 8a6c688a56 Assert parsed audit JSON structure
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 0eabe4b3ab Assert import count > 0 in options-forwarding test
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 8ce5da65e2 Pin Brave search error body assertion
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina cedb86af5d Pin OpenAI API error assertions to their shapes
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 6adfbea336 Pin Records.populate_genres error to OpenAI prefix
Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 0ee0dcb118 Match decoded OpenAI error body, not raw JSON
Switch the 500-response stubs to Req.Test.json so Req decodes the
body, then match the map shape. Avoids coupling the assertion to
the exact JSON serialization.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 1429f7a826 Drop tautological delete.id checks
Repo.delete always returns the struct it was given, so deleted.id
== original.id is trivially true. Thread deleted.id through the
trailing get_*! call instead, so the binding is load-bearing.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bb260437a1 Pin populate_genres error to OpenAI prefix
Match the string prefix "OpenAI API error:" that propagates from
OpenAI.gpt on non-2xx responses.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina a841ccf8e8 Tighten generate_record_embedding assertions
Assert stored embedding text matches the representation helper.
Pin the OpenAI-500 error path to the raw JSON body string.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina bbe0d5cd9e Pin artist_refresh_discogs_data assertions
Bind the returned ArtistInfo. For the no-discogs branch, assert
discogs_data was not touched.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina f9e3e8c5f8 Pin artist_refresh_wikipedia_data assertions
Bind perform_job results and assert id equality. For the unchanged case,
also assert wikipedia_data is the same as before the job ran.

Refs #176
2026-04-20 06:50:04 +01:00
Claudio Ortolina 914fb4fbee Match specific errors on scrobble_rules duplicate
Pin the duplicate-rule error to the match_value field and "has already been
taken" message, matching the unique_constraint error_key in the changeset.

Refs #176
2026-04-20 06:50:04 +01:00