Commit Graph

107 Commits

Author SHA1 Message Date
Claudio Ortolina d5be6e6182 Remove on tour tracking functionality (source unreliable) 2026-03-24 15:01:16 +00:00
Claudio Ortolina 9e1dbfd530 Re-enable Credo ModuleDoc check
Closes #108
2026-03-13 10:52:01 +00:00
Claudio Ortolina 24863b16c4 Remove unused Logger requires 2026-03-12 14:57:48 +00:00
Claudio Ortolina 7cf9b4e7f8 First pass at uniformed types, specs and docs
- spec public functions (skipping controllers, views, live views and
components)
- use types instead of explanations in docs
- remove redundant docs
- fix typos
2026-03-06 08:33:11 +00:00
Claudio Ortolina eb8a00ab54 Replace String.to_integer with safe Integer.parse
Use Integer.parse/1 instead of String.to_integer/1 on user-supplied
and external API input to prevent ArgumentError crashes on non-numeric
values. Adds fallback defaults or nil returns at each call site.
2026-03-05 14:27:37 +00:00
Claudio Ortolina a992c1f0dd Introduce a Req.RateLimiter module to fix API rate limit issues 2026-03-02 15:04:45 +00:00
Claudio Ortolina 919a42d355 Parameterize API cooldowns 2026-03-01 17:17:32 +00:00
Claudio Ortolina b35f4aed1d EXP: enrich artist info data via Last.fm and produce better embeddings 2026-03-01 08:22:35 +00:00
Claudio Ortolina dfbc284211 Increase timeout when scrobbling tracks 2026-02-27 16:48:27 +00:00
Claudio Ortolina 47edcad245 Update module docs 2026-02-17 09:01:12 +00:00
Claudio Ortolina c6b5cc590e Remove unnecessary TODO
When calling refresh synchronously, the caller decides what to do with
the error
2026-02-17 08:11:29 +00:00
Claudio Ortolina bc5a0b9df1 Don't use primary key for scrobbled tracks embeds 2025-10-22 16:51:55 +01:00
Claudio Ortolina cc84c3a429 Remove unused functions 2025-09-26 15:26:44 +03:00
Claudio Ortolina 455c3a8b53 Serve artist images via assets 2025-09-18 21:26:20 +03:00
Claudio Ortolina dae334b1b7 Scrobbled tracks CRUD
- Failing tests
- Warnings
2025-09-16 12:45:36 +03:00
Claudio Ortolina 2ee1706c1a Skip now playing tracks when pulling from Last.fm 2025-09-09 13:30:46 +03:00
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