-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
299 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
//! Contains template for /album routes | ||
#![allow(clippy::module_name_repetitions, reason = "looks nicer")] | ||
|
||
use crate::{artist::ArtistSelectionTemplate, encode_url, not_found, AppState}; | ||
|
||
use std::sync::Arc; | ||
|
||
use axum::{ | ||
extract::{Path, Query, State}, | ||
response::{IntoResponse, Redirect, Response}, | ||
}; | ||
use endsong::prelude::*; | ||
use rinja_axum::Template; | ||
use serde::Deserialize; | ||
use tracing::debug; | ||
|
||
/// To choose an artist and album if there are multiple with same capitalization | ||
#[derive(Deserialize)] | ||
pub struct AlbumQuery { | ||
/// The artist's index in the [`Vec`] returned by [`find::artist`] | ||
artist_id: Option<usize>, | ||
/// The albums's index in the [`Vec`] returned by [`find::album`] | ||
album_id: Option<usize>, | ||
} | ||
|
||
/// [`Template`] for if there are multiple artist with different | ||
/// capitalization in [`base`] | ||
#[derive(Template)] | ||
#[template(path = "album_selection.html", print = "none")] | ||
struct AlbumSelectionTemplate { | ||
/// Albums with same name, but different capitalization | ||
/// | ||
/// Will only happen if you didn't do [`SongEntries::sum_different_capitalization`] | ||
/// | ||
/// See [`find::album`] | ||
albums: Vec<Album>, | ||
/// Link to the album page (without `album_id`) | ||
link_base_album: String, | ||
} | ||
/// [`Template`] for [`base`] | ||
#[derive(Template)] | ||
#[template(path = "album.html", print = "none")] | ||
struct AlbumTemplate<'a> { | ||
/// Reference to the given Album | ||
album: &'a Album, | ||
/// This album's playcount | ||
plays: usize, | ||
/// Percentage of this album's plays to the total playcount | ||
percentage_of_plays: String, | ||
/// Percentage of this album's plays to the artist playcount | ||
percentage_of_artist_plays: String, | ||
/// Time spent listening to this artist | ||
time_played: TimeDelta, | ||
/// Date of first artist entry | ||
first_listen: DateTime<Local>, | ||
/// Date of most recent artist entry | ||
last_listen: DateTime<Local>, | ||
/// Link to artist page | ||
link_artist: String, | ||
} | ||
/// GET `/album/[:artist_name]/[:album_name][?artist_id=usize][?album_id=usize]` | ||
/// | ||
/// Artist page | ||
/// | ||
/// Returns an [`AlbumTemplate`] with a valid `artist_name` and `album_name`, | ||
/// an [`ArtistSelectionTemplate`] if there are | ||
/// multiple artists with this name | ||
/// but different capitalization, | ||
/// an [`AlbumSelectionTemplate`] if there are | ||
/// multiple artists with this name | ||
/// but different capitalization, | ||
/// and [`not_found`] if the artist or album is not in the dataset | ||
#[expect(clippy::cast_precision_loss, reason = "necessary for % calc")] | ||
#[expect( | ||
clippy::missing_panics_doc, | ||
reason = "unwraps which should never panic" | ||
)] | ||
pub async fn base( | ||
State(state): State<Arc<AppState>>, | ||
Path((artist_name, album_name)): Path<(String, String)>, | ||
Query(options): Query<AlbumQuery>, | ||
) -> Response { | ||
debug!( | ||
artist_name = artist_name, | ||
album_name = album_name, | ||
artist_id = options.artist_id, | ||
album_id = options.album_id, | ||
"/album/[:artist_name]/[:album_name][?artist_id=usize][?album_id=usize]" | ||
); | ||
|
||
let entries = &state.entries; | ||
|
||
let Some(artists) = entries.find().artist(&artist_name) else { | ||
return not_found().await.into_response(); | ||
}; | ||
|
||
let artist = if artists.len() == 1 { | ||
artists.first() | ||
} else if let Some(artist_id) = options.artist_id { | ||
artists.get(artist_id) | ||
} else { | ||
None | ||
}; | ||
|
||
let Some(artist) = artist else { | ||
// query if multiple artists with different capitalization | ||
return ArtistSelectionTemplate { | ||
link_base_artist: format!( | ||
"/album/{}/{}", | ||
encode_url(&artist_name), | ||
encode_url(&album_name) | ||
), | ||
artists, | ||
} | ||
.into_response(); | ||
}; | ||
|
||
let Some(albums) = entries.find().album(&album_name, &artist_name) else { | ||
return not_found().await.into_response(); | ||
}; | ||
|
||
let album = if albums.len() == 1 { | ||
albums.first() | ||
} else if let Some(album_id) = options.album_id { | ||
albums.get(album_id) | ||
} else { | ||
None | ||
}; | ||
|
||
let encoded_artist = encode_url(&artist.name); | ||
|
||
let Some(album) = album else { | ||
let encoded_album = encode_url(&albums.first().unwrap().name); | ||
|
||
let link_base_album = if let Some(artist_id) = options.artist_id { | ||
format!("/album/{encoded_artist}/{encoded_album}?artist_id={artist_id}") | ||
} else { | ||
format!("/album/{encoded_artist}/{encoded_album}") | ||
}; | ||
|
||
return AlbumSelectionTemplate { | ||
albums, | ||
link_base_album, | ||
} | ||
.into_response(); | ||
}; | ||
|
||
// http://localhost:3000/album/TiA/%E6%B5%81%E6%98%9F | ||
// (i.e. could only happen on manual link entry) | ||
if &album.artist != artist { | ||
return Redirect::permanent(&format!( | ||
"/artist/{encoded_artist}?artist_id={}", | ||
options.artist_id.unwrap() | ||
)) | ||
.into_response(); | ||
} | ||
|
||
let plays = gather::plays(entries, album); | ||
let percentage_of_plays = format!( | ||
"{:.2}", | ||
(plays as f64 / gather::all_plays(entries) as f64) * 100.0 | ||
); | ||
let percentage_of_artist_plays = format!( | ||
"{:.2}", | ||
(plays as f64 / gather::plays(entries, artist) as f64) * 100.0 | ||
); | ||
|
||
// unwrap ok bc already made sure artist exists earlier | ||
let first_listen = entries | ||
.iter() | ||
.find(|entry| album.is_entry(entry)) | ||
.unwrap() | ||
.timestamp; | ||
let last_listen = entries | ||
.iter() | ||
.rev() | ||
.find(|entry| album.is_entry(entry)) | ||
.unwrap() | ||
.timestamp; | ||
|
||
let link_artist = if let Some(artist_id) = options.artist_id { | ||
format!("/artist/{encoded_artist}?artist_id={artist_id}") | ||
} else { | ||
format!("/artist/{encoded_artist}") | ||
}; | ||
|
||
AlbumTemplate { | ||
plays, | ||
percentage_of_plays, | ||
percentage_of_artist_plays, | ||
time_played: gather::listening_time(entries, album), | ||
first_listen, | ||
last_listen, | ||
link_artist, | ||
album, | ||
} | ||
.into_response() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{% extends "base.html" %} | ||
<!-- --> | ||
{% block title %} {{ album.artist.name }} - {{ album.name }}{% endblock %} | ||
<!-- --> | ||
{% block content %} | ||
<h2 class="text-2xl font-bold"> | ||
<a href="{{ link_artist }}">{{ album.artist.name }}</a> - {{ album.name }} | ||
</h2> | ||
<section class="flex"> | ||
<article | ||
class="flex w-full flex-col gap-3 rounded-lg border border-gray-200 p-7 shadow dark:shadow-none" | ||
> | ||
<h2 class="self-center text-xl font-semibold">General info</h2> | ||
<ul class="list-none"> | ||
<li>Playcount: {{ plays }}</li> | ||
<li> | ||
Time spent listening: | ||
<ul class="ml-4 list-disc"> | ||
{% let minutes = time_played.num_minutes() %} {% let hours = | ||
time_played.num_hours() %} {% let days = time_played.num_days() %} | ||
<li> | ||
<time datetime="{{ time_played }}" | ||
>{{ minutes }} minute{{ minutes|pluralize }}</time | ||
> | ||
</li> | ||
{% if hours != 0 %} | ||
<li> | ||
<time datetime="{{ time_played }}" | ||
>or {{ hours }} hour{{ hours|pluralize }}</time | ||
> | ||
</li> | ||
{% endif %} {% if days != 0 %} | ||
<li> | ||
<time datetime="{{ time_played }}" | ||
>or {{ days }} day{{ days|pluralize }}</time | ||
> | ||
</li> | ||
{% endif %} | ||
</ul> | ||
</li> | ||
<li> | ||
First listen: | ||
<time datetime="{{ first_listen }}">{{ first_listen }}</time> | ||
</li> | ||
<li> | ||
Last listen: | ||
<time datetime="{{ last_listen }}">{{ last_listen }}</time> | ||
</li> | ||
<li>% of total plays: {{ percentage_of_plays }}%</li> | ||
<li>% of {{ album.artist }} plays: {{ percentage_of_artist_plays }}%</li> | ||
</ul> | ||
</article> | ||
</section> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{% extends "base.html" %} | ||
<!-- --> | ||
{% block title %} Album Selection {% endblock %} | ||
<!-- --> | ||
{% block content %} | ||
<h2 class="text-2xl">Which album do you mean?</h2> | ||
<ul class="list-none"> | ||
{% for album in albums %} | ||
<li> | ||
<a href="{{ link_base_album }}?album_id={{ loop.index0 }}" | ||
>{{ album.name }}</a | ||
> | ||
</li> | ||
{% endfor %} | ||
</ul> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters