Commit Graph

288 Commits

Author SHA1 Message Date
Claudio Ortolina b2bc5f07f7 Remove unnecessary code 2025-07-03 21:10:59 +01:00
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 d5dc07d66c Add Meer to polyfillable artists 2025-06-24 09:16:58 +03:00
Claudio Ortolina a9d27059c4 Update dependencies
circular_buffer 0.4.1 => 0.4.2
ecto 3.12.6 => 3.13.1
ecto_sql 3.12.1 => 3.13.1
ecto_sqlite3 0.19.0 => 0.20.0 (minor)
exqlite 0.31.0 => 0.32.0 (minor)

Includes handling of deprecation for literal/1 to identifier/1
2025-06-21 09:10:57 +03:00
Claudio Ortolina 0739e12ad3 Polyfill scrobbled tracks every hour 2025-06-20 22:43:04 +03:00
Claudio Ortolina 35e060613b Add function to backfill missing data in scrobbled tracks 2025-06-19 17:38:51 +03:00
Claudio Ortolina 25634f5ee7 Exclude counts without album title from top albums 2025-06-19 17:21:16 +03:00
Claudio Ortolina f3425d8cb9 Add top artist and top albums of all time
Executed by Claude
2025-06-19 17:17:51 +03:00
Claudio Ortolina 9b20db762d Show top artists in stats page 2025-06-16 16:47:35 +03:00
Claudio Ortolina 24b34d57d2 Update dependencies
floki 0.37.1 => 0.38.0 (minor)
quokka 2.6.0 => 2.7.0

Includes application of expensive enum check quokka rule
2025-06-16 11:47:07 +03:00
Claudio Ortolina 753615c57f Improve top albums
Makes it timezone aware, so that it can round up to the beginning of day
2025-06-15 12:12:04 +03:00
Claudio Ortolina aeedfd5f04 EXP: use a larger image for color frequency extractor
So that it catches smaller areas.
2025-06-14 23:10:09 +03:00
Claudio Ortolina 2df5e7ee1d Show wishlisted date for wishlisted records 2025-06-14 12:58:47 +03:00
Claudio Ortolina a1f5182e90 Make top albums clickable 2025-06-14 11:16:51 +03:00
Claudio Ortolina 945dc91887 Make cutoff time deterministic 2025-06-14 10:48:15 +03:00
Claudio Ortolina 268aff9918 Add top albums to the stats page
Authored with Claude.

> I'd like to have a new section in the Stats page. In this section I
want to see the top albums I listened to in the past 30,
  90, 365 days. Use the data in `scrobbled_tracks` to determine which
albums need to be displayed.

> This all looks great as a starter. I'd like to make some
modifications.

  1. Invert the position of the artist name and the album title (so that
it's consistent with the Scrobble Activity)
  2. Remove the border and shadow from around each album
  3. Rename the "Last 365 days" section to "Last Year"
2025-06-14 09:38:44 +03:00
Claudio Ortolina 2cc1d7ca8f Pick color extraction strategy from dropdown 2025-06-08 20:06:54 +01:00
Claudio Ortolina 2bbb91769d Generate better dominant colors async after record import 2025-06-08 07:50:07 +01:00
Claudio Ortolina 569f8b5340 Distinguish between color extraction strategies
By default, use fast color sampling. Run heavy edge weighted extraction
only on demand.
2025-06-08 07:43:07 +01:00
Claudio Ortolina 96ceb5c7f6 Switch to edge-weighted color extraction algo 2025-06-07 21:53:47 +01:00
Claudio Ortolina 378992e632 Add functions to generate dominant colors 2025-06-07 20:13:19 +01:00
Claudio Ortolina c5e47cbc8c Display dominant colors from the db 2025-06-07 20:07:23 +01:00
Claudio Ortolina 8a2fda5f73 Increase sample rate to get a more faithful set of colors 2025-06-07 19:43:54 +01:00
Claudio Ortolina ccb3089ddf Experiment: show dominant colors in collected record details page 2025-06-07 14:36:29 +01:00
Claudio Ortolina a63f2de60b Use ->> instead of json_extract in queries
Improves readability of queries.
2025-06-06 21:05:44 +01:00
Claudio Ortolina fd997e853f Update artists when refreshing MusicBrainz data 2025-06-04 15:57:25 +01:00
Claudio Ortolina 27c1cc0eda Import artist joinphrase 2025-06-03 18:16:47 +01:00
Claudio Ortolina 669b144c55 Find last listened track based on title and artist as well 2025-06-03 18:05:23 +01:00
Claudio Ortolina c23260d03d Show last listen at for each collected record 2025-06-03 15:57:12 +01:00
Claudio Ortolina 1f64c71efa Apply quokka blocks rule 2025-05-28 19:51:54 +01:00
Claudio Ortolina 676c2e4290 Enable quokka with :module_directives rule 2025-05-28 19:41:57 +01:00
Claudio Ortolina 423d1e7764 Can sort wishlisted records by insertion 2025-05-26 23:12:34 +01:00
Claudio Ortolina ad29b8f96d Normalized search index
So that one can search for 'bjorn' and find 'Bjørn'
2025-05-25 09:26:01 +01:00
Claudio Ortolina b6c554af34 Display rich release summary in form and details 2025-05-25 08:45:38 +01:00
Claudio Ortolina 7172551552 Poor man's rate limiting 2025-05-24 18:28:06 +01:00
Claudio Ortolina ee808fb041 Add ability to refresh musicbrainz data async 2025-05-24 18:04:20 +01:00
Claudio Ortolina b44693b022 Move Artist and ArtistInfo under Artists context 2025-05-23 20:11:57 +01:00
Claudio Ortolina d38fcd6fee Account for no artist country or area 2025-05-21 22:35:45 +01:00
Claudio Ortolina 051c6195b5 Harden release label display 2025-05-18 20:57:17 +01:00
Claudio Ortolina 083b245862 Fallback to country when areas have no country data 2025-05-16 10:25:22 +01:00
Claudio Ortolina 157b408341 Fix Artists.exists?/1 to account for multiple records 2025-05-13 10:22:23 +01:00
Claudio Ortolina b831a2e1d7 Can scrobble individual discs 2025-05-10 09:46:07 +01:00
Claudio Ortolina f223d6d739 Rename scrobble -> scrobble_release and tighten options check 2025-05-10 08:08:42 +01:00
Claudio Ortolina 80ec4336f6 Account for artist info areas having iso-3166-2-codes 2025-05-09 11:27:47 +01:00
Claudio Ortolina ee4d9dd29a Set the selected release id when scanning a wishlisted record 2025-05-09 10:10:33 +01:00
Claudio Ortolina ebaaa8d95b Sort releases in edit form select 2025-05-07 15:01:30 +01:00
Claudio Ortolina fddc2dcbf0 Link Last.fm account from tracks 2025-05-07 11:26:41 +01:00
Claudio Ortolina 952d3388a1 Scrobble using finished_at time 2025-05-07 10:46:51 +01:00
Claudio Ortolina a94c349781 Add context function to scrobble a release 2025-05-07 09:07:45 +01:00