Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Johnstone
c8e78606b1 Clippy 2026-03-19 15:07:30 -04:00
10 changed files with 20 additions and 114 deletions

View File

@@ -85,18 +85,6 @@ pub async fn get_me() -> Result<UserInfo, ApiError> {
get_json(&format!("{BASE}/auth/me")).await
}
pub async fn list_users() -> Result<Vec<UserInfo>, ApiError> {
get_json(&format!("{BASE}/auth/users")).await
}
pub async fn create_user(username: &str, password: &str) -> Result<UserInfo, ApiError> {
let body = serde_json::json!({"username": username, "password": password}).to_string();
post_json(&format!("{BASE}/auth/users"), &body).await
}
pub async fn delete_user(id: i32) -> Result<(), ApiError> {
delete(&format!("{BASE}/auth/users/{id}")).await
}
// --- Lyrics ---
pub async fn get_lyrics(artist: &str, title: &str) -> Result<LyricsResult, ApiError> {
@@ -142,10 +130,6 @@ pub async fn list_artists(limit: u64, offset: u64) -> Result<Vec<ArtistListItem>
get_json(&format!("{BASE}/artists?limit={limit}&offset={offset}")).await
}
pub async fn get_artist(id: i32) -> Result<ArtistDetail, ApiError> {
get_json(&format!("{BASE}/artists/{id}")).await
}
pub async fn get_artist_full(id: &str) -> Result<FullArtistDetail, ApiError> {
get_json(&format!("{BASE}/artists/{id}/full")).await
}
@@ -158,9 +142,6 @@ pub async fn get_album(mbid: &str) -> Result<MbAlbumDetail, ApiError> {
get_json(&format!("{BASE}/albums/{mbid}")).await
}
pub async fn list_tracks(limit: u64, offset: u64) -> Result<Vec<Track>, ApiError> {
get_json(&format!("{BASE}/tracks?limit={limit}&offset={offset}")).await
}
// --- Watchlist ---
pub async fn add_artist(name: &str, mbid: Option<&str>) -> Result<AddSummary, ApiError> {
@@ -183,13 +164,6 @@ pub async fn add_album(
post_json(&format!("{BASE}/albums"), &body).await
}
pub async fn get_watchlist() -> Result<Vec<WatchListEntry>, ApiError> {
get_json(&format!("{BASE}/watchlist")).await
}
pub async fn remove_watchlist(id: i32) -> Result<(), ApiError> {
delete(&format!("{BASE}/watchlist/{id}")).await
}
// --- Downloads ---
pub async fn get_downloads(status: Option<&str>) -> Result<Vec<DownloadItem>, ApiError> {
@@ -249,9 +223,6 @@ pub async fn trigger_organize() -> Result<TaskRef, ApiError> {
post_empty(&format!("{BASE}/organize")).await
}
pub async fn get_task(id: &str) -> Result<TaskInfo, ApiError> {
get_json(&format!("{BASE}/tasks/{id}")).await
}
pub async fn get_config() -> Result<AppConfig, ApiError> {
get_json(&format!("{BASE}/config")).await

View File

@@ -1,3 +1,2 @@
pub mod navbar;
pub mod status_badge;
pub mod watch_indicator;

View File

@@ -1,25 +0,0 @@
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct Props {
pub status: String,
}
#[function_component(WatchIndicator)]
pub fn watch_indicator(props: &Props) -> Html {
let (icon, color, title) = match props.status.as_str() {
"owned" => ("", "var(--success)", "Owned"),
"partial" => ("", "var(--warning)", "Partial"),
"wanted" => ("", "var(--accent)", "Wanted"),
"downloading" => ("", "var(--accent)", "Downloading"),
"fully_watched" => ("", "var(--accent)", "Fully watched"),
"unwatched" => ("", "var(--text-muted)", "Not watched"),
_ => ("", "var(--text-muted)", "Unknown"),
};
html! {
<span style={format!("color: {color}; font-size: 1.1em; cursor: help;")} title={title}>
{ icon }
</span>
}
}

View File

@@ -82,7 +82,7 @@ pub fn album_page(props: &Props) -> Html {
<tbody>
{ for d.tracks.iter().map(|t| {
let duration = t.duration_ms
.map(|ms| fmt_duration(ms))
.map(&fmt_duration)
.unwrap_or_default();
let track_key = t.recording_mbid.clone();

View File

@@ -58,14 +58,11 @@ pub fn artist_page(props: &Props) -> Html {
// Phase 2: if not enriched, fetch full data in background
if needs_enrich && !user_acted.get() {
match api::get_artist_full(&id).await {
Ok(full) => {
// Only apply if user hasn't triggered a refresh
if !user_acted.get() {
detail.set(Some(full));
}
if let Ok(full) = api::get_artist_full(&id).await {
// Only apply if user hasn't triggered a refresh
if !user_acted.get() {
detail.set(Some(full));
}
Err(_) => {} // quick data is still showing, don't overwrite with error
}
}
}
@@ -326,8 +323,7 @@ pub fn artist_page(props: &Props) -> Html {
<td>
if tc > 0 {
<span class="text-sm" style={
if album.watched_tracks >= tc { "color: var(--accent);" }
else if album.watched_tracks > 0 { "color: var(--accent);" }
if album.watched_tracks > 0 { "color: var(--accent);" }
else { "color: var(--text-muted);" }
}>
{ format!("{}/{}", album.watched_tracks, tc) }

View File

@@ -10,7 +10,7 @@ pub fn downloads_page() -> Html {
let items = use_state(|| None::<Vec<DownloadItem>>);
let error = use_state(|| None::<String>);
let message = use_state(|| None::<String>);
let dl_query = use_state(|| String::new());
let dl_query = use_state(String::new);
let refresh = {
let items = items.clone();

View File

@@ -2,7 +2,6 @@ use yew::prelude::*;
use yew_router::prelude::*;
use crate::api;
use crate::components::watch_indicator::WatchIndicator;
use crate::pages::Route;
use crate::types::ArtistListItem;
@@ -73,8 +72,7 @@ pub fn library_page() -> Html {
<td>
if a.total_items > 0 {
<span class="text-sm" style={
if a.total_watched >= a.total_items { "color: var(--accent);" }
else if a.total_watched > 0 { "color: var(--accent);" }
if a.total_watched > 0 { "color: var(--accent);" }
else { "color: var(--text-muted);" }
}>
{ format!("{}/{}", a.total_watched, a.total_items) }

View File

@@ -16,7 +16,7 @@ enum SearchResults {
#[function_component(SearchPage)]
pub fn search_page() -> Html {
let query = use_state(|| String::new());
let query = use_state(String::new);
let search_type = use_state(|| "artist".to_string());
let results = use_state(|| SearchResults::None);
let error = use_state(|| None::<String>);

View File

@@ -97,16 +97,6 @@ pub struct LyricsResult {
pub synced_lyrics: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Album {
pub id: i32,
pub name: String,
pub album_artist: String,
pub year: Option<i32>,
pub genre: Option<String>,
pub musicbrainz_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Track {
pub id: i32,
@@ -120,18 +110,6 @@ pub struct Track {
pub codec: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct ArtistDetail {
pub artist: Artist,
pub albums: Vec<Album>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct AlbumDetail {
pub album: Album,
pub tracks: Vec<Track>,
}
/// Album detail from MusicBrainz (the primary album view).
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct MbAlbumDetail {
@@ -182,17 +160,6 @@ pub struct TrackResult {
pub score: u8,
}
// --- Watchlist ---
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct WatchListEntry {
pub id: i32,
pub item_type: String,
pub name: String,
pub artist_name: Option<String>,
pub status: String,
}
// --- Downloads ---
#[derive(Debug, Clone, PartialEq, Deserialize)]

View File

@@ -623,16 +623,16 @@ async fn fetch_wikipedia_data(
let cache_key = format!("artist_wiki:{mbid}");
// Check cache first
if let Ok(Some(json)) = queries::cache::get(state.db.conn(), &cache_key).await {
if let Ok(cached) = serde_json::from_str::<serde_json::Value>(&json) {
return (
cached
.get("photo_url")
.and_then(|v| v.as_str())
.map(String::from),
cached.get("bio").and_then(|v| v.as_str()).map(String::from),
);
}
if let Ok(Some(json)) = queries::cache::get(state.db.conn(), &cache_key).await
&& let Ok(cached) = serde_json::from_str::<serde_json::Value>(&json)
{
return (
cached
.get("photo_url")
.and_then(|v| v.as_str())
.map(String::from),
cached.get("bio").and_then(|v| v.as_str()).map(String::from),
);
}
// Find Wikipedia URL from artist info — try direct link first, then resolve via Wikidata
@@ -641,7 +641,7 @@ async fn fetch_wikipedia_data(
Some(u.url.clone())
} else if let Some(wd) = info.urls.iter().find(|u| u.link_type == "wikidata") {
// Extract Wikidata entity ID and resolve to Wikipedia URL
let entity_id = wd.url.split('/').last().unwrap_or("");
let entity_id = wd.url.split('/').next_back().unwrap_or("");
resolve_wikidata_to_wikipedia(entity_id).await
} else {
None