Commit Graph

148 Commits

Author SHA1 Message Date
Claudio Ortolina 024ba38b63 Use Ecto.UUID instead of hand-rolled type 2025-07-03 21:07:42 +01:00
Claudio Ortolina d91685b646 First rough implementation of scrobble rules (Claude)
This plan outlines the implementation of functionality to manage
arbitrary rules for fixing data in the `scrobbled_tracks` table. The
system will support two types of transformations:

1. **Album Rule**: Update the album MusicBrainz ID of all tracks
belonging to a specific album (identified by album title)
2. **Artist Rule**: Update the artist MusicBrainz ID of all tracks
belonging to a specific artist (identified by artist name)

- `scrobbled_tracks` table exists with the following structure:
  - `scrobbled_at_uts` (integer, primary key)
  - `musicbrainz_id` (string) - track MusicBrainz ID
  - `title` (string) - track title
  - `cover_url` (string)
  - `scrobbled_at_label` (string)
  - `artist` (map) - embedded artist data with `musicbrainz_id` and
`name`
  - `album` (map) - embedded album data with `musicbrainz_id` and
`title`
  - `last_fm_data` (map) - raw Last.fm API response

- `LastFm.Track` - Schema for scrobbled tracks
- `LastFm.Artist` - Embedded artist schema
- `LastFm.Album` - Embedded album schema

- Main navigation has Stats, Collection, Wishlist sections
- "More" dropdown contains development tools and utilities
- Uses Phoenix LiveView for interactive components

**File**:
`priv/repo/migrations/YYYYMMDDHHMMSS_create_scrobble_rules.exs`

Create `scrobble_rules` table with:

- `id` (primary key)
- `type` (string) - "album" or "artist"
- `match_value` (string) - album title or artist name to match
- `target_musicbrainz_id` (string) - MusicBrainz ID to set
- `enabled` (boolean) - whether rule is active
- `description` (text) - optional description for the rule
- `inserted_at` (timestamp)
- `updated_at` (timestamp)

Indexes:

- `type, match_value` (compound index for efficient matching)
- `enabled` (for filtering active rules)

**File**: `lib/music_library/scrobble_rules/scrobble_rule.ex`

Create Ecto schema with:

- Field definitions matching migration
- Validations:
  - `type` must be "album" or "artist"
  - `match_value` required and non-empty
  - `target_musicbrainz_id` required and non-empty
  - `enabled` defaults to true
- Changeset functions for create/update operations

**File**: `lib/music_library/scrobble_rules.ex`

Implement context functions:

- `list_scrobble_rules(opts \\ [])` - list all rules with optional
filtering
- `get_scrobble_rule!(id)` - get specific rule by ID
- `create_scrobble_rule(attrs)` - create new rule
- `update_scrobble_rule(rule, attrs)` - update existing rule
- `delete_scrobble_rule(rule)` - delete rule
- `change_scrobble_rule(rule, attrs \\ %{})` - create changeset
- `list_enabled_rules()` - get only enabled rules for worker
- `apply_album_rule(rule)` - apply album transformation
- `apply_artist_rule(rule)` - apply artist transformation
- `apply_all_rules()` - apply all enabled rules

**File**: `lib/music_library_web/components/layouts/app.html.heex`

Add new navigation link in the "More" dropdown:

- Icon: `hero-adjustments-horizontal`
- Label: "Scrobble Rules"
- Route: `/scrobble-rules`
- Position: After "Oban" link, before separator

**File**: `lib/music_library_web/router.ex`

Add new LiveView routes in the authenticated scope:

- `/scrobble-rules` - index page
- `/scrobble-rules/new` - create new rule
- `/scrobble-rules/:id/edit` - edit existing rule

**File**: `lib/music_library_web/live/scrobble_rules_live/index.ex`

Implement LiveView with:

- List view showing all rules with status indicators
- Create/Edit forms with validation
- Delete confirmation
- Filter controls (by type, enabled/disabled)
- Manual rule application triggers
- Real-time updates via PubSub

**Template**:
`lib/music_library_web/live/scrobble_rules_live/index.html.heex`

- Table layout with rule details
- Action buttons (Edit, Delete, Apply)
- Form components for create/edit
- Status indicators for enabled/disabled rules

**File**:
`lib/music_library_web/live/scrobble_rules_live/form_component.ex`

Create reusable form component:

- Type selection (album/artist)
- Match value input with validation
- Target MusicBrainz ID input
- Description textarea
- Enable/disable toggle
- Save/Cancel actions

Where possible, use the components available at
<https://docs.fluxonui.com/1.1.4/overview.html>.

**File**: `lib/music_library/scrobble_rules/worker.ex`

Implement Oban worker:

- Scheduled to run periodically (e.g., every 30 minutes)
- Fetches all enabled rules
- Applies each rule in sequence
- Logs application results
- Handles errors gracefully
- Tracks last application time

**File**: `test/music_library/scrobble_rules_test.exs`

- Test all context functions
- Test rule application logic
- Test edge cases and error handling

**File**: `test/music_library/scrobble_rules/scrobble_rule_test.exs`

- Test schema validations
- Test changeset functions
- Test constraint validations

**File**: `test/music_library_web/live/scrobble_rules_live_test.exs`

- Test LiveView interactions
- Test form submissions
- Test real-time updates
- Test navigation and routing

**File**: `test/music_library/scrobble_rules/worker_test.exs`

- Test job execution
- Test rule application
- Test error handling
- Test scheduling

- Ensure atomic operations when applying rules
- Validate MusicBrainz IDs before applying
- Log all transformations for audit trail

- Index optimization for rule matching
- Batch processing for large datasets
- Rate limiting for external API calls

- Clear feedback on rule application
- Preview mode to show what would change
- Bulk operations for managing multiple rules
- Export/import functionality for rule sets

- Input validation and sanitization
- Rate limiting on rule creation
- Audit logging for all changes

```sql
-- scrobble_rules table
CREATE TABLE scrobble_rules (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  type TEXT NOT NULL CHECK (type IN ('album', 'artist')),
  match_value TEXT NOT NULL,
  target_musicbrainz_id TEXT NOT NULL,
  enabled BOOLEAN NOT NULL DEFAULT TRUE,
  description TEXT,
  inserted_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_scrobble_rules_type_match ON scrobble_rules(type,
match_value);
CREATE INDEX idx_scrobble_rules_enabled ON scrobble_rules(enabled);
```

```
lib/music_library/
├── scrobble_rules/
│   ├── scrobble_rule.ex          # Schema
│   └── worker.ex                 # Oban worker
└── scrobble_rules.ex             # Context

lib/music_library_web/
└── live/
    └── scrobble_rules_live/
        ├── index.ex              # LiveView
        ├── index.html.heex       # Template
        └── form_component.ex     # Form component

priv/repo/migrations/
└── YYYYMMDDHHMMSS_create_scrobble_rules.exs

test/
├── music_library/
│   ├── scrobble_rules_test.exs
│   └── scrobble_rules/
│       ├── scrobble_rule_test.exs
│       └── worker_test.exs
└── music_library_web/
    └── live/
        └── scrobble_rules_live_test.exs
```

1. Database migration and schema
2. Context module with basic CRUD operations
3. LiveView interface for rule management
4. Navigation and routing updates
5. Periodic worker implementation
6. Comprehensive testing
7. Documentation and deployment

This plan ensures a robust, maintainable solution that integrates
seamlessly with the existing Music Library application architecture.
2025-07-03 16:47:08 +01:00
Claudio Ortolina 08bc50d5dd Use tabs for scrobble activity toggle 2025-06-25 12:05:23 +03:00
Claudio Ortolina adc44e056e Reduce data size to speedup test
Suggested and executed by Claude
2025-06-14 21:15:26 +03:00
Claudio Ortolina 07d9db6011 Speed up tests by caching cover data
Suggested and executed by Claude
2025-06-14 21:15:06 +03:00
Claudio Ortolina 1d78f6c4dc Make test faster by deterministically reducing size 2025-06-14 20:04:09 +03:00
Claudio Ortolina d7060697b2 Format dates in release summary and label 2025-06-01 16:58:04 +01:00
Claudio Ortolina 06511f89db Cache Last.fm tracks raw data when parsing api response 2025-05-30 16:52:07 +01:00
Claudio Ortolina 676c2e4290 Enable quokka with :module_directives rule 2025-05-28 19:41:57 +01:00
Claudio Ortolina 1298d711a3 Remove unnecessary selected_release_label/1 2025-05-25 08:56:24 +01:00
Claudio Ortolina b44693b022 Move Artist and ArtistInfo under Artists context 2025-05-23 20:11:57 +01:00
Claudio Ortolina 80ca186ba8 Render artist bio in a left-side sheet 2025-05-21 21:24:15 +01:00
Claudio Ortolina cc3cf02490 Show artist MusicBrainz ID (with link and copy button) 2025-05-15 16:59:12 +01:00
Claudio Ortolina f21a289008 Load release when opening collection show page
Includes small fixes to make both logic and tests more robust
2025-05-11 11:04:28 +01:00
Claudio Ortolina 38f49e9a90 Skip failing test
To fix, need to move the test to a separate component test
2025-05-09 21:04:28 +01:00
Claudio Ortolina 327fd231cf Test that side panel renders tracks 2025-05-08 09:45:13 +01:00
Claudio Ortolina f4fe638a95 Display tracks for records with a selected release 2025-05-04 21:10:40 +01:00
Claudio Ortolina 2e5c90593e Make test completely deterministic 2025-05-04 16:47:17 +01:00
Claudio Ortolina bc213caa45 Store selected release id when scanning a record 2025-05-01 19:36:47 +01:00
Claudio Ortolina 3e7341d4ca Support choosing a selected release 2025-05-01 17:42:40 +01:00
Claudio Ortolina e083950138 Rename record.release to record.release_date
Includes changes to dependent tables (i.e. the record search index) and
related functions.
2025-05-01 14:04:57 +01:00
Claudio Ortolina f1dd45e438 Show country flag for artist 2025-04-29 09:58:06 +01:00
Claudio Ortolina 995773f673 Default covers to 2000px 2025-04-14 10:58:21 +01:00
Claudio Ortolina 8b846298a7 Make collection ordered by purchase by default 2025-04-04 13:58:25 +01:00
Claudio Ortolina 02dafb1a9f Extend assertions for scrobble activity test
Tests that information actually changes when toggling from albums to
tracks.
2025-03-22 08:31:30 +00:00
Claudio Ortolina 22de7ed85f Default displaying albums in scrobble activity 2025-03-22 08:24:22 +00:00
Claudio Ortolina c3420189f7 Update dependencies
plug 1.16.1 => 1.17.0

Includes fix for `use Plug.Test` deprecation.
2025-03-14 19:20:02 +00:00
Claudio Ortolina 3d0eec3bff Add /api/collection endpoint 2025-03-10 11:17:51 +00:00
Claudio Ortolina 91a1141441 Rename Import -> Add, and avoid surfacing MusicBrainz where unneeded 2025-03-07 15:10:22 +00:00
Claudio Ortolina fee2799d50 Show barcodes that are not found inside the results UI
Includes a refactor to extract/cleanup barcode scan logic from the
component to a separate context with a better API.
2025-03-07 11:22:23 +00:00
Claudio Ortolina 93f6b260b2 Extract test fixture for release_group_releases 2025-03-01 07:40:00 +00:00
Claudio Ortolina a218942110 Remove mox in favour of built-in Req stubs 2025-02-28 16:41:16 +00:00
Claudio Ortolina 3d11cd585d Use built-in Req stub for LastFm tests 2025-02-27 13:43:49 +00:00
Claudio Ortolina 25567eeb26 Extract trigger_hook/3-4 test helper 2025-02-25 14:03:08 +00:00
Claudio Ortolina c67d11da0c Show similar artists in artist page
Styling to be defined - only links for now.
2025-02-23 16:56:52 +00:00
Claudio Ortolina 7df136c890 Sort aliases 2025-02-20 15:27:01 +00:00
Claudio Ortolina 163bbe8902 Add test for importing a record via barcode scan 2025-02-18 20:40:04 +00:00
Claudio Ortolina 94a2784020 Move function in MusicBrainz main module 2025-02-17 22:07:49 +00:00
Claudio Ortolina e218b06350 Rename APIBehaviourMock -> APIMock 2025-02-17 21:41:50 +00:00
Claudio Ortolina 513ce6363f Rename RecordsFixtures -> Fixtures.Records 2025-02-17 16:08:19 +00:00
Claudio Ortolina ea7b5b646a Namespace MusicBrainz fixtures 2025-02-17 16:03:23 +00:00
Claudio Ortolina d27f3ccf72 Namespace Last.Fm fixtures 2025-02-17 15:52:54 +00:00
Claudio Ortolina 522f80194d Remove visible camera status text 2025-02-15 18:08:30 +00:00
Claudio Ortolina d077f08afa Start making it more robust
- Use structured data for search/render
- Start testing component
- Start testing domain logic
2025-02-15 18:08:30 +00:00
Claudio Ortolina 6ad28f1271 When importing a record, stream all its releases 2025-02-05 15:31:08 +00:00
Claudio Ortolina f29dd1f0ab Fetch 100 releases when importing a record
The release group contains only 25 releases, which breaks tracking
scrobble status. Albums like "Toto IV" or "Hounds of Love" end up
looking like they're not part of the collections because the individual
releases are not stored.

Still error prone, because this change doesn't paginate to more than 100
records, but that will be addressed in a future commit.
2025-02-05 10:12:27 +00:00
Claudio Ortolina acd52978ae Revise naming and notification around purchasing records 2025-02-04 12:11:16 +00:00
Claudio Ortolina f21729ac00 Cache covers for 1 year 2025-01-31 07:43:06 +00:00
Claudio Ortolina 9e1f00c9a8 Refactor type/format labels to enable translations 2025-01-29 11:04:19 +00:00
Claudio Ortolina 7ce2b6a7b2 Add wishlist#show test 2025-01-27 23:33:11 +00:00