Commit Graph

621 Commits

Author SHA1 Message Date
Claudio Ortolina f75e34211b Update dependencies
mix:
  decimal 3.1.0 => 3.1.1
  oban 2.22.1 => 2.23.0
2026-05-27 20:36:16 +03:00
Claudio Ortolina 65b2f3e1f2 Update dependencies
ex_slop 0.4.1 => 0.4.2

Introduces a new check around misusing the `length/1` function which
require widespread fixes.
2026-05-25 19:45:33 +03:00
Claudio Ortolina f347f56218 ML-169.8: reduce Stats mount sync queries to 3
Move 7 non-critical queries out of synchronous mount into assign_async
and start_async tasks with skeleton loading placeholders.

Only scalar badge counters (collection count, wishlist count, scrobble
count) remain synchronous. All async sections use <.async_result> with
loading and failed slots. Scrobble activity preserves LiveView streams
via handle_async. On-this-day date changes use AsyncResult.ok/2.
2026-05-25 09:31:43 +03:00
Claudio Ortolina f1c9069c35 Expand enrichment query scope and streamline selected release 2026-05-22 13:40:40 +01:00
Claudio Ortolina ff3f809c2c ML-170: add enrichment fields to collection API
Add MusicLibrary.Collection.Enrichment module with batch scrobble, artist
country, and selected release enrichment from existing tables.

Wire enrichment into all four collection API endpoints (index, latest,
random, on_this_day). New JSON fields: scrobble_count, last_listened_at,
artist_country, selected_release.

Uses 3 fixed-count batch queries regardless of page size — no N+1 risk.
No schema changes or migrations required.
2026-05-22 13:21:14 +01:00
Claudio Ortolina d8a67cd475 ML-181: harden assets endpoints 2026-05-22 08:20:35 +01:00
Claudio Ortolina ff42f24972 ML-195: Add credo check for put_toast/put_toast! usage in live components 2026-05-21 08:08:11 +01:00
Claudio Ortolina f108e6f062 ML-174: add empty action to record set dropdown menu
Add RecordSets.empty_record_set/1 that bulk-deletes all items in a set via
a single DELETE query and returns the reloaded empty set. Add Empty button
with confirmation prompt to both index and show view dropdowns.
2026-05-14 17:15:06 +01:00
Claudio Ortolina a59dd22a18 ML-168: broadcast index_changed event after background import
Import workers now broadcast :records_index_changed on
"records:index_changed" after successful import via
Records.broadcast_index_changed/0.

CollectionLive.Index and WishlistLive.Index subscribe to the
topic in mount/3 and reload their record streams on receipt,
with a live_action guard to skip reloads when the grid is
hidden behind a modal (:import, :barcode_scan).

IndexActions.handle_index_changed/1 refreshes total_entries
before reloading to keep the pagination bar accurate.
2026-05-14 17:15:06 +01:00
Claudio Ortolina d30e6d2d8f Add Credo refactor checks and fix double Enum.map
Add 9 enabled + 1 disabled Credo checks from ex_slop README.
Fix MapMap finding in ListeningStats.update/1 (single pass).
Disable AppendSingleItem (10 pre-existing violations).
2026-05-14 10:47:13 +01:00
Claudio Ortolina 533fe8a8c4 Streamline function implementation (no double negative) 2026-05-13 20:14:58 +01:00
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