94 Commits

Author SHA1 Message Date
Claudio Ortolina f57485e509 Allow manual upload of artist image 2025-07-06 16:57:30 +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 e678d533f5 Mount BackgroundRepo in Ecto stats 2025-06-01 16:34:58 +01:00
Claudio Ortolina 676c2e4290 Enable quokka with :module_directives rule 2025-05-28 19:41:57 +01:00
Claudio Ortolina bb4fce61c7 Extract StaticAssets hook with a single default live_session 2025-05-13 21:03:59 +01:00
Claudio Ortolina 7c547f2d80 Redirect to Last.fm and receive a token (unused) 2025-05-05 19:19:12 +01:00
Claudio Ortolina 3d3e758a7a Add endpoint to display an artist image 2025-04-27 21:20:38 +01:00
Claudio Ortolina 7c0cee4732 Mount oban_web 2025-04-14 17:14:39 +01:00
Claudio Ortolina a836782016 Wishlist missing records from the artist page 2025-04-09 11:03:31 +01:00
Claudio Ortolina 3d0eec3bff Add /api/collection endpoint 2025-03-10 11:17:51 +00:00
Claudio Ortolina b111558f07 Use a plug for Prometheus metrics
The plug is injected as early as possible in order to respond quickly,
without polluting logs.
2025-03-04 20:21:15 +00:00
Claudio Ortolina 7c6fe3c0d2 Mount metrics in application
Avoids pulling entire TelemetryMetricsPrometheus dependency.
2025-03-04 17:55:07 +00:00
Claudio Ortolina a626c68d65 Open scan modal, handle permissions, open/close video preview 2025-02-15 18:08:30 +00:00
Claudio Ortolina f82f2d447c Mount /api/backup route 2025-01-30 14:38:02 +00:00
Claudio Ortolina b47f282223 Add database backup functionality 2025-01-30 09:39:45 +00:00
Claudio Ortolina 4a8a37a52f Tighten up monitoring routes configuration 2025-01-28 11:49:45 +00:00
Claudio Ortolina 309ca8c67a Expose stats for MusicLibrary.ErrorRepo in dashboard
No need for metrics as the repo is very low in usage.
2025-01-23 11:42:18 +00:00
Claudio Ortolina d194a54760 Adds API to return a random record from the collection 2025-01-01 19:28:49 +00:00
Claudio Ortolina 5cd57fdde4 Install Error Tracker to track production issues 2025-01-01 17:11:17 +00:00
Claudio Ortolina 97f26e0cd2 Serve covers under API namespace as well 2024-12-22 22:15:24 +00:00
Claudio Ortolina f1af38606a Protect API behind a bearer token 2024-12-22 18:32:04 +00:00
Claudio Ortolina cf3761ca79 Fold RequireLogin plug into Auth module 2024-12-22 16:12:31 +00:00
Claudio Ortolina 4f456322fc Bootstrap the /api namespace 2024-12-21 14:38:39 +00:00
Claudio Ortolina 0eaaab7d00 Not working 2024-12-02 12:40:09 +00:00
Claudio Ortolina edb24dc932 Keep telemetry metrics history 2024-11-25 10:02:34 +00:00
Claudio Ortolina f157fbadee Enable live dashboard in production
Includes extras to monitor ecto repos.
2024-11-23 19:26:54 +00:00
Claudio Ortolina 5b3aab51ed Change routes from "/records/*" to "/collection/*" 2024-11-20 15:43:25 +00:00
Claudio Ortolina b8f3bb9b2c Promote Collection to proper namespace 2024-11-20 15:37:04 +00:00
Claudio Ortolina 7d574c7fd9 Convert stats page to a live view 2024-10-29 16:32:34 +00:00
Claudio Ortolina 4213f24ab8 Enable edit/delete/import records in the wishlist 2024-10-21 18:51:26 +01:00
Claudio Ortolina 020430cdb4 Add wishlist show 2024-10-21 18:39:45 +01:00
Claudio Ortolina 9b305ff06a Add wishlist section with paginated list 2024-10-21 18:25:56 +01:00
Claudio Ortolina 4fcfc6c921 Complete authentication flow with dummy password 2024-10-20 19:25:31 +01:00
Claudio Ortolina b5bceb92e0 Add login route with hardcoded password validation 2024-10-20 19:05:41 +01:00
Claudio Ortolina ac27e86fb5 Rename /images to /covers 2024-10-06 16:06:33 +01:00
Claudio Ortolina 8d6bfaf035 Put some stats on the home page 2024-09-28 22:41:53 +01:00
Claudio Ortolina 1ec6712513 Add health controller
Fly.io uses '/health' to determine the application health status.
2024-09-28 15:27:59 +01:00
Claudio Ortolina 60939a304b Remove autogenerated home route
Renders records instead.
2024-09-27 19:59:15 +01:00
Claudio Ortolina 12bb923aec Rename /search to /import 2024-09-24 22:49:39 +01:00
Claudio Ortolina 089e61a574 Remove manual record creation 2024-09-24 09:27:35 +01:00
Claudio Ortolina d97f21a8ef Can search and import records
Requires refactor and cleanup, along with integration tests
2024-09-23 20:27:40 +01:00
Claudio Ortolina afc1802399 Serve local cover images 2024-09-16 09:50:07 +01:00
Claudio Ortolina e08fbe3740 Generate schema and live_view scaffold for records
1. Records are not yet tied to artists
2. The generated edit route, along with related functionality, may be
   deleted as I don't think it's gonna be needed. Leaving it included in
   the commit for reference.
2024-09-12 14:51:58 +01:00
Claudio Ortolina d547ee6f8d Generate project
Using `mix phx.new music_library --database sqlite3 --binary-id --no-mailer`
2024-09-12 14:05:08 +01:00