Commit Graph

610 Commits

Author SHA1 Message Date
Claudio Ortolina be7a9d5990 Start PubSub before Oban to prevent registry errors
Phoenix.PubSub must be initialized (creating its internal Registry)
before Oban starts processing jobs. With one_for_one strategy,
children start sequentially; previously Oban was started first,
causing 'unknown registry: MusicLibrary.PubSub' errors when a
job called broadcast before PubSub was ready (error #3891).
2026-05-08 14:10:56 +01:00
Claudio Ortolina 5f240fe573 Fix stale PubSub subscriptions on record navigation
Subscription management was only done in mount/3, meaning
navigating to a different record via handle_params/3 left the
old subscription active. A background update for the old record
would then overwrite the currently displayed record.

Fix: manage subscriptions in handle_params/3 (unsubscribe old,
subscribe new) via shared RecordActions.manage_subscription/2,
and guard handle_info({:update,...}) against mismatched IDs.
2026-05-07 10:14:10 +01:00
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 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 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 08fcd70a8c ML-154: add early platform detection for SQLite extensions 2026-04-30 13:39:39 +01:00
Claudio Ortolina 13068aa0ac ML-153: move MusicBrainz data transforms from Record 2026-04-30 13:21:18 +01:00
Claudio Ortolina b8b3b24ffa ML-151: document Assets.Cache TTL and invalidation strategy
Expand @moduledoc with cache key structure, 7-day TTL via
@one_week_seconds, periodic pruning by PruneAssetCache (every 12h),
and rationale for TTL-only invalidation (content-addressable immutable
assets). Update architecture.md entry accordingly.
2026-04-30 13:07:06 +01:00
Claudio Ortolina 4075ec45ac Make sure creating artist infos returns all fields 2026-04-26 19:24:56 +01:00
Claudio Ortolina b64ab98189 Extract to pipe 2026-04-26 19:08:21 +01:00
Claudio Ortolina eb9b1866a8 Streamline function implementation 2026-04-26 19:05:27 +01:00
Claudio Ortolina 4fb1bf5079 Remove duplication in implementation 2026-04-25 21:34:11 +01:00
Claudio Ortolina d4132d8851 Uniform interface of lastfm refresh async function 2026-04-25 21:30:06 +01:00
Claudio Ortolina 9113df5879 ML-146: honour API retry headers 2026-04-25 07:39:49 +01:00
Claudio Ortolina ab4eaa8890 Use if instead of case -> nil 2026-04-25 06:37:02 +01:00
Claudio Ortolina a1c665b490 ML-21: Classify API errors as transient vs permanent 2026-04-24 14:02:15 +01:00
Claudio Ortolina 2e38412a84 ML-10: break compile cycle via Artists → Collection edge 2026-04-24 10:49:47 +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 dd1d99a145 Fix store_embedding/3 spec to reflect float list input 2026-04-21 12:57:28 +01:00
Claudio Ortolina 30c0a961e9 Stringify format atom in get_release_status query 2026-04-21 12:57:09 +01:00
Claudio Ortolina 9840690af1 Fix nil-index crash in move_record_in_set 2026-04-21 12:47:57 +01:00
Claudio Ortolina 53348a328e Relax ScrobbleRule.t to allow unpersisted structs 2026-04-21 12:47:38 +01:00
Claudio Ortolina 87d2a8f186 Fix Maintenance vacuum/optimize return specs 2026-04-21 12:47:18 +01:00
Claudio Ortolina 40ac5bdc99 Widen Transform.new/1 spec to accept keyword list 2026-04-21 12:47:00 +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 45236e4b10 Convert ErrorIgnorer tests to doctests 2026-04-19 07:50:39 +01:00
Claudio Ortolina bbe1722c9f Handle unencodable structs in QueryReporter params 2026-04-17 07:10:45 +01:00
Claudio Ortolina 58c60193f2 Use pipe 2026-04-16 10:38:08 +01:00
Claudio Ortolina 2124c982f7 Handle possible non-existent record id in search filter 2026-04-15 15:10:28 +01:00
Claudio Ortolina c816f2de4a Add scrobbled tracks search parser
Introduces ListeningStats.SearchParser with NimbleParsec supporting
record:, album_mbid:, artist_mbid:, artist:, album:, track: filters.
Links scrobble count on record page to filtered scrobbled tracks view.
2026-04-15 14:56:38 +01:00
Claudio Ortolina b9612a2a26 Fix records on this day for partial release dates
SQLite interprets bare years (e.g. "1970") as Julian day numbers
in strftime, producing wrong month-day matches. Filter to only
full YYYY-MM-DD dates before comparing.
2026-04-15 07:29:58 +01:00
Claudio Ortolina d75508531e Generate parse_type/parse_format from module attributes 2026-04-14 16:56:04 +01:00
Claudio Ortolina 2bb755b1c8 Parse matching record fields in Records.Record 2026-04-14 16:51:06 +01:00
Claudio Ortolina 888b5a68e6 Update all callers to use matching_records
Migrate StatsLive.Index, ScrobbledTracksLive.Index, and TopAlbums to
consume matching_records list instead of scalar record IDs. Remove the
derive_legacy_record_ids bridge function. TopAlbums now uses a dropdown
for multi-record badges with play count display.
2026-04-14 16:14:59 +01:00
Claudio Ortolina 2c6e6e7ca2 Return matching_records list from top_albums_attach_metadata
Apply the same release-group JSON aggregation pattern to top albums
queries. Add JSON parsing in get_top_albums/1 and
get_top_albums_by_days/2.
2026-04-14 16:05:51 +01:00
Claudio Ortolina cfd267627c Return matching_records list from tracks_with_record_info_query
Replace scalar collected_record_id/wishlisted_record_id subqueries with
a single matching_records JSON array grouped by release group
(records.musicbrainz_id). Add parse_matching_records/1 for JSON parsing
and derive_legacy_record_ids/2 as a temporary bridge for existing
templates. Update tests to use shared musicbrainz_id fixtures.
2026-04-14 14:17:02 +01:00
Claudio Ortolina 9f4135661e Replace scalar record IDs with matching_records JSON array in tracks query
Replaces collected_record_id and wishlisted_record_id correlated
subqueries in tracks_with_record_info_query with a single
matching_records json_group_array subquery that returns all records
sharing the same release ID. Adds parse_matching_records/1 for JSON
deserialization. Updates recent_activity/2 and list_tracks/1 to parse
the JSON at the boundary. Derives legacy collected_record_id and
wishlisted_record_id from matching_records so existing LiveViews keep
working. The get_top_albums test is temporarily skipped pending the
top_albums_attach_metadata update.
2026-04-14 14:12:14 +01:00
Claudio Ortolina 6a6410bcaa Have the LLM use square bracketed links for internal references 2026-04-13 14:24:36 +01:00
Claudio Ortolina 96bb0fc403 Try and control ChatGPT's tone and "helpfulness" 2026-04-13 14:09:43 +01:00
Claudio Ortolina 9b44b7050f Count unique catalog entries in collection summary
The record_count now reflects the number of grouped catalog lines
the model sees, not raw DB rows. Two copies of the same album on
different formats count as 1 catalog entry.
2026-04-13 13:12:30 +01:00
Claudio Ortolina 54dab5fbdf Use gpt 5.1 to cater for collection summary context size 2026-04-13 12:43:42 +01:00
Claudio Ortolina f9448cac6f Revert "Use smaller model so that we don't exhaust tokens"
This reverts commit 9c0e11bc84.
2026-04-13 12:34:17 +01:00