Added auth

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

View File

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

View File

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