Commit Graph

91 Commits

Author SHA1 Message Date
Claudio Ortolina 233e3091b6 Support applying scrobble rules only on a given set of tracks 2025-08-13 14:00:24 +03:00
Claudio Ortolina 1ca1dcf0f3 Fix signature of LastFm.Feed.update/1 2025-08-11 11:59:44 +03:00
Claudio Ortolina 71e5a8d860 Apply scrobble rules after refreshing the feed 2025-08-03 15:49:14 +03: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 124d539cec Enable other quokka directives 2025-06-14 20:10:22 +03:00
Claudio Ortolina 46681deca9 Improve track refresh logic 2025-06-02 21:00:44 +01:00
Claudio Ortolina 3e9c719ce8 LastFm pull 100 tracks as a default 2025-06-02 20:53:04 +01:00
Claudio Ortolina c4fa389e5d Remove obsolete HACK comment 2025-06-01 14:18:25 +01:00
Claudio Ortolina 1703f1cf70 Use stored scrobbled tracks 2025-05-31 21:44:54 +01:00
Claudio Ortolina 32956ca593 Backfill faster 2025-05-31 20:55:13 +01:00
Claudio Ortolina d3855d989a Tweak Last.Fm API timeouts for batch operations 2025-05-31 20:54:56 +01:00
Claudio Ortolina b191d87dfd Fix scrobble tracks schema
Remove the primary key, and just set a unique index on scrobbled time
and title, which allows skipping duplicates.
2025-05-31 20:54:37 +01:00
Claudio Ortolina 54ee77995a Avoid primary key clashes 2025-05-31 20:24:29 +01:00
Claudio Ortolina 750f0a74d1 Add logic to backfill scrobbled_tracks
Chokes on duplicate primary keys
2025-05-31 20:12:36 +01:00
Claudio Ortolina a27b38f192 Add logic to import a batch of Last.fm tracks 2025-05-31 16:16:24 +01:00
Claudio Ortolina d01d16f1ae Add infrastructure to persist scrobble tracks to database
Unused for now.
2025-05-31 08:16:32 +01:00
Claudio Ortolina 46ffa375af Use embedded schemas for LastFm track, artist and album
Preparation for persisting the scrobble activity to database
2025-05-30 17:01:12 +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 ae34909c1f Apply quokka single_node rule 2025-05-28 19:50:06 +01:00
Claudio Ortolina 774f1c82e5 Apply quokka pipes rule 2025-05-28 19:48:35 +01:00
Claudio Ortolina 676c2e4290 Enable quokka with :module_directives rule 2025-05-28 19:41:57 +01:00
Claudio Ortolina cbf12e6026 Add documentation for LastFm.Session parsing 2025-05-26 23:24:31 +01:00
Claudio Ortolina a2cec1fdb9 Improve clarity fo comment 2025-05-26 23:21:34 +01:00
Claudio Ortolina 64d99af9dc Remove redundant Last.fm req option which causes timeouts 2025-05-13 06:54:41 +01:00
Claudio Ortolina bed107b031 Reduce Last.fm API pool idle timeout time to force re-connections
Changing the maximum number of milliseconds that a pool can be idle
before being terminated, Setting it to a low value so that it gets
terminated and we don't get failures after the vm resumes.
2025-05-09 09:18:01 +01:00
Claudio Ortolina 8477517e5c Add LastFm.Scrobble abstraction 2025-05-07 09:02:52 +01:00
Claudio Ortolina fcdae8f925 Extract LastFm.API.Signature 2025-05-07 08:31:39 +01:00
Claudio Ortolina c7491719e3 Extract LastFm.API.ErrorResponse 2025-05-07 08:24:47 +01:00
Claudio Ortolina 83d13da679 Can scrobble track maps 2025-05-07 08:11:44 +01:00
Claudio Ortolina d5b29b20bb Store last fm information into session (doesn't work yet) 2025-05-05 20:58:37 +01:00
Claudio Ortolina 95e80bc432 Parse Last.fm session 2025-05-05 20:25:30 +01:00
Claudio Ortolina 299cc468b6 Add Last.Fm function to get session 2025-05-05 20:07:18 +01:00
Claudio Ortolina 7c547f2d80 Redirect to Last.fm and receive a token (unused) 2025-05-05 19:19:12 +01:00
Claudio Ortolina 3f4e2bc975 Link to Last.fm events from "On Tour" badge 2025-04-11 10:27:45 +01:00
Claudio Ortolina 9f13d090ed Use more precise types 2025-04-07 19:27:17 +01:00
Claudio Ortolina 1d2d72b8ce Improve retry strategies for Last.fm refresh 2025-04-06 20:52:57 +01:00
Claudio Ortolina bc72ab7e7f Document Last.fm refresh module 2025-04-03 09:44:42 +01:00
Claudio Ortolina 828f1667bf Remove unused LastFm.Feed functions 2025-03-14 11:53:54 +00:00
Claudio Ortolina 0020687f7b Include artist information in all albums feed 2025-03-11 08:48:17 +00:00
Claudio Ortolina ec10447ca0 Extend Last.fm Feed album/artist functions 2025-03-10 17:53:19 +00:00
Claudio Ortolina 500455481f Set the LastFm http client to terminate idle connections after 30 secs
This change attempts at solving the issue of the application being woken
up after suspension and being unable to talk to the LastFm API (i.e.
throwing plenty of Mint/timeout errors).

The theory is that with this setting the application should quickly
enough restart the pool after waking up.
2025-03-01 07:25:05 +00:00
Claudio Ortolina 47bfe736ef Remove unused LastFm.Finch pool 2025-02-27 16:29:34 +00:00
Claudio Ortolina 7ac2f7a815 Remove LastFm.APIBehaviour 2025-02-27 13:43:49 +00:00
Claudio Ortolina 3d11cd585d Use built-in Req stub for LastFm tests 2025-02-27 13:43:49 +00:00
Claudio Ortolina 522b3034aa Rewrite LastFm.APIImpl using Req 2025-02-27 13:43:48 +00:00
Claudio Ortolina eb6d49fe96 Allow longer LastFm refresh times 2025-02-27 13:43:48 +00:00
Claudio Ortolina 825c00dd50 Add summary to artist 2025-02-23 18:53:28 +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 828563e933 Shorten timeouts for Last.fm api
Especially when the application wakes up from suspension, requests tend
to timeout.
2025-02-09 08:07:27 +00:00
Claudio Ortolina 0d77fdc031 Display badge when an artist is touring 2025-01-25 08:50:46 +00:00