Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Johnstone
0f066d5708 Added auth 2026-03-19 14:02:24 -04:00
3 changed files with 46 additions and 42 deletions

View File

@@ -60,26 +60,22 @@ impl fmt::Display for LibrarySummary {
}
/// Add an artist to the watchlist by expanding into individual track wanted items.
///
/// Fetches the artist's discography from the provider, then for each release
/// fetches the tracklist and adds each track as a separate Wanted item.
pub async fn add_artist(
conn: &DatabaseConnection,
name: Option<&str>,
musicbrainz_id: Option<&str>,
provider: &impl MetadataProvider,
user_id: Option<i32>,
) -> WatchResult<AddSummary> {
let (resolved_name, resolved_mbid) =
resolve_artist_info(name, musicbrainz_id, provider).await?;
let artist = queries::artists::upsert(conn, &resolved_name, resolved_mbid.as_deref()).await?;
// Get artist MBID — either provided or from the search
let artist_mbid = resolved_mbid.or_else(|| artist.musicbrainz_id.clone());
let artist_mbid = match artist_mbid {
Some(mbid) => mbid,
None => {
// Search for the artist to get MBID
let results = provider
.search_artist(&resolved_name, 1)
.await
@@ -95,7 +91,6 @@ pub async fn add_artist(
tracing::info!(name = %resolved_name, mbid = %artist_mbid, "fetching discography");
// Fetch all releases
let releases = provider
.get_artist_releases(&artist_mbid, 100)
.await
@@ -123,6 +118,7 @@ pub async fn add_artist(
&resolved_name,
&track.title,
Some(&track.recording_mbid),
user_id,
)
.await
{
@@ -141,19 +137,17 @@ pub async fn add_artist(
}
/// Add an album to the watchlist by expanding into individual track wanted items.
///
/// Fetches the album's tracklist and adds each track as a separate Wanted item.
pub async fn add_album(
conn: &DatabaseConnection,
artist_name: Option<&str>,
album_name: Option<&str>,
musicbrainz_id: Option<&str>,
provider: &impl MetadataProvider,
user_id: Option<i32>,
) -> WatchResult<AddSummary> {
let (resolved_album, resolved_artist, resolved_mbid) =
resolve_album_info(artist_name, album_name, musicbrainz_id, provider).await?;
// Get release MBID
let release_mbid = match resolved_mbid {
Some(mbid) => mbid,
None => {
@@ -185,6 +179,7 @@ pub async fn add_album(
&resolved_artist,
&track.title,
Some(&track.recording_mbid),
user_id,
)
.await
{
@@ -208,6 +203,7 @@ pub async fn add_track(
title: Option<&str>,
musicbrainz_id: Option<&str>,
provider: &impl MetadataProvider,
user_id: Option<i32>,
) -> WatchResult<WatchListEntry> {
let (resolved_title, resolved_artist, resolved_mbid) =
resolve_track_info(artist_name, title, musicbrainz_id, provider).await?;
@@ -217,12 +213,15 @@ pub async fn add_track(
let item = queries::wanted::add(
conn,
ItemType::Track,
&resolved_title,
resolved_mbid.as_deref(),
Some(artist.id),
None,
None,
queries::wanted::AddWantedItem {
item_type: ItemType::Track,
name: &resolved_title,
musicbrainz_id: resolved_mbid.as_deref(),
artist_id: Some(artist.id),
album_id: None,
track_id: None,
user_id,
},
)
.await?;
@@ -250,18 +249,22 @@ async fn add_track_inner(
artist_name: &str,
title: &str,
recording_mbid: Option<&str>,
user_id: Option<i32>,
) -> WatchResult<bool> {
let artist = queries::artists::upsert(conn, artist_name, None).await?;
let is_owned = matching::track_is_owned(conn, artist_name, title).await?;
let item = queries::wanted::add(
conn,
ItemType::Track,
title,
recording_mbid,
Some(artist.id),
None,
None,
queries::wanted::AddWantedItem {
item_type: ItemType::Track,
name: title,
musicbrainz_id: recording_mbid,
artist_id: Some(artist.id),
album_id: None,
track_id: None,
user_id,
},
)
.await?;
@@ -288,7 +291,6 @@ async fn resolve_artist_info(
let mbid =
mbid.ok_or_else(|| WatchError::Other("either a name or --mbid is required".into()))?;
// Search for artist by MBID to get the name
let results = provider
.search_artist(mbid, 1)
.await
@@ -385,12 +387,12 @@ pub async fn list_items(
conn: &DatabaseConnection,
status_filter: Option<WantedStatus>,
artist_filter: Option<&str>,
user_id: Option<i32>,
) -> WatchResult<Vec<WatchListEntry>> {
let items = queries::wanted::list(conn, status_filter).await?;
let items = queries::wanted::list(conn, status_filter, user_id).await?;
let mut entries = Vec::new();
for item in items {
// Use the name field directly now
let artist_name = if let Some(id) = item.artist_id {
queries::artists::get_by_id(conn, id)
.await
@@ -400,7 +402,6 @@ pub async fn list_items(
None
};
// Apply artist filter if provided
if let Some(filter) = artist_filter {
let filter_norm = matching::normalize(filter);
let matches = artist_name
@@ -434,7 +435,7 @@ pub async fn remove_item(conn: &DatabaseConnection, id: i32) -> WatchResult<()>
/// Get a summary of the library state.
pub async fn library_summary(conn: &DatabaseConnection) -> WatchResult<LibrarySummary> {
let all = queries::wanted::list(conn, None).await?;
let all = queries::wanted::list(conn, None, None).await?;
let mut summary = LibrarySummary::default();
for item in &all {

View File

@@ -131,7 +131,7 @@ async fn main() -> anyhow::Result<()> {
anyhow::bail!("provide either a name or --mbid");
}
let summary =
add_artist(db.conn(), name.as_deref(), mbid.as_deref(), &mb_client).await?;
add_artist(db.conn(), name.as_deref(), mbid.as_deref(), &mb_client, None).await?;
println!("Artist watch: {summary}");
}
AddCommand::Album {
@@ -148,6 +148,7 @@ async fn main() -> anyhow::Result<()> {
album.as_deref(),
mbid.as_deref(),
&mb_client,
None,
)
.await?;
println!("Album watch: {summary}");
@@ -166,6 +167,7 @@ async fn main() -> anyhow::Result<()> {
title.as_deref(),
mbid.as_deref(),
&mb_client,
None,
)
.await?;
println!(
@@ -179,7 +181,7 @@ async fn main() -> anyhow::Result<()> {
},
Commands::List { status, artist } => {
let status_filter = status.as_deref().map(parse_status).transpose()?;
let entries = list_items(db.conn(), status_filter, artist.as_deref()).await?;
let entries = list_items(db.conn(), status_filter, artist.as_deref(), None).await?;
if entries.is_empty() {
println!("Watchlist is empty.");

View File

@@ -118,7 +118,7 @@ async fn test_add_track_wanted() {
let db = test_db().await;
let provider = MockProvider;
let entry = add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider)
let entry = add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider, None)
.await
.unwrap();
assert_eq!(entry.item_type, ItemType::Track);
@@ -133,7 +133,7 @@ async fn test_add_track_auto_owned() {
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
let entry = add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, &provider)
let entry = add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, &provider, None)
.await
.unwrap();
assert_eq!(entry.status, WantedStatus::Owned);
@@ -150,12 +150,13 @@ async fn test_add_album_expands_to_tracks() {
Some("Test Album"),
None,
&provider,
None,
)
.await
.unwrap();
assert_eq!(summary.tracks_added, 2);
let items = list_items(db.conn(), Some(WantedStatus::Wanted), None)
let items = list_items(db.conn(), Some(WantedStatus::Wanted), None, None)
.await
.unwrap();
assert_eq!(items.len(), 2);
@@ -167,12 +168,12 @@ async fn test_add_artist_expands_to_tracks() {
let db = test_db().await;
let provider = MockProvider;
let summary = add_artist(db.conn(), Some("Test Artist"), None, &provider)
let summary = add_artist(db.conn(), Some("Test Artist"), None, &provider, None)
.await
.unwrap();
assert_eq!(summary.tracks_added, 2);
let items = list_items(db.conn(), None, None).await.unwrap();
let items = list_items(db.conn(), None, None, None).await.unwrap();
assert_eq!(items.len(), 2);
}
@@ -181,22 +182,22 @@ async fn test_list_items_with_filters() {
let db = test_db().await;
let provider = MockProvider;
add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider)
add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider, None)
.await
.unwrap();
add_track(db.conn(), Some("Tool"), Some("Lateralus"), None, &provider)
add_track(db.conn(), Some("Tool"), Some("Lateralus"), None, &provider, None)
.await
.unwrap();
let all = list_items(db.conn(), None, None).await.unwrap();
let all = list_items(db.conn(), None, None, None).await.unwrap();
assert_eq!(all.len(), 2);
let wanted = list_items(db.conn(), Some(WantedStatus::Wanted), None)
let wanted = list_items(db.conn(), Some(WantedStatus::Wanted), None, None)
.await
.unwrap();
assert_eq!(wanted.len(), 2);
let radiohead = list_items(db.conn(), None, Some("Radiohead"))
let radiohead = list_items(db.conn(), None, Some("Radiohead"), None)
.await
.unwrap();
assert_eq!(radiohead.len(), 1);
@@ -207,11 +208,11 @@ async fn test_remove_item() {
let db = test_db().await;
let provider = MockProvider;
let entry = add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider)
let entry = add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider, None)
.await
.unwrap();
remove_item(db.conn(), entry.id).await.unwrap();
let all = list_items(db.conn(), None, None).await.unwrap();
let all = list_items(db.conn(), None, None, None).await.unwrap();
assert!(all.is_empty());
}
@@ -222,10 +223,10 @@ async fn test_library_summary() {
insert_track(&db, "Pink Floyd", "Time", "DSOTM").await;
add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider)
add_track(db.conn(), Some("Radiohead"), Some("Creep"), None, &provider, None)
.await
.unwrap();
add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, &provider)
add_track(db.conn(), Some("Pink Floyd"), Some("Time"), None, &provider, None)
.await
.unwrap();