Skip to content

Commit

Permalink
Move routes to modules
Browse files Browse the repository at this point in the history
  • Loading branch information
fsktom committed Oct 11, 2024
1 parent dc5ded0 commit 5988e06
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 252 deletions.
36 changes: 24 additions & 12 deletions endsong_web/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion endsong_web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ axum = { version = "0.7", features = ["form"] }
tower-http = { version = "0.6", features = ["compression-br"] }
endsong = { path = ".." }
itertools = "0.13"
rinja = { version = "0.3", features = ["with-axum"] }
rinja = { version = "0.3", features = ["with-axum", "code-in-doc"] }
rinja_axum = "0.3"
tokio = { version = "1.40", features = ["full"] }
tracing = "0.1"
Expand Down
84 changes: 84 additions & 0 deletions endsong_web/src/artist.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Contains templates for `/artist` route
use crate::*;

use std::sync::Arc;

use axum::{
extract::{Path, Query, State},
response::{IntoResponse, Response},
};
use endsong::prelude::*;
use rinja_axum::Template;
use serde::Deserialize;
use tracing::debug;

/// To choose an artist if there are multiple with same capitalization
/// (in my dataset tia)
#[derive(Deserialize)]
pub struct ArtistQuery {
id: usize,
}
/// [`Template`] for if there are multiple artist with different
/// capitalization in [`artist`]
#[derive(Template)]
#[template(path = "artist_selection.html", print = "none")]
struct ArtistSelectionTemplate {
artists: Vec<Artist>,
}
/// [`Template`] for [`artist`]
#[derive(Template)]
#[template(path = "artist.html", print = "none")]
struct ArtistTemplate<'a> {
artist: &'a Artist,
plays: usize,
time_played: TimeDelta,
}
/// GET `/artist/:artist_name(?id=usize)`
///
/// Artist page
///
/// Returns an [`ArtistTemplate`] with a valid `artist_name`,
/// an [`ArtistSelectionTemplate`] if there are
/// multiple artists with this name
/// but different capitalization,
/// and [`not_found`] if it's not in the dataset
pub async fn base(
State(state): State<Arc<AppState>>,
Path(artist_name): Path<String>,
options: Option<Query<ArtistQuery>>,
) -> Response {
debug!(
artist_name = artist_name,
query = options.is_some(),
"GET /artist/:artist_name(?query)"
);

let entries = state.entries.read().await;

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(Query(options)) = options {
artists.get(options.id)
} else {
None
};

let artist = if let Some(artist) = artist {
artist
} else {
// query if multiple artists with different capitalization
return ArtistSelectionTemplate { artists }.into_response();
};

ArtistTemplate {
plays: gather::plays(&entries, artist),
time_played: gather::listening_time(&entries, artist),
artist,
}
.into_response()
}
74 changes: 74 additions & 0 deletions endsong_web/src/artists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Contains templates for `/artists`` routes
use crate::AppState;

use std::sync::Arc;

use axum::{
extract::{Form, State},
response::IntoResponse,
};
use rinja::Template;
use serde::Deserialize;
use tracing::debug;

/// [`Template`] for [`base`]
#[derive(Template)]
#[template(path = "artists.html", print = "none")]
struct BaseTemplate {}
/// GET `/artists`
///
/// List of artists (HTML Template will call [`elements`] on-load)
pub async fn base() -> impl IntoResponse {
debug!("GET /artists");

BaseTemplate {}
}

#[derive(Deserialize)]
pub struct ArtistListForm {
search: String,
}
/// [`Template`] for [`elements`]
///
/// Template:
/// ```rinja
/// {% for artist in artist_names %}
/// <li><a href="/artist/{{ artist|encodeurl }}">{{ artist }}</a></li>
/// {% endfor %}
/// ```
#[derive(Template)]
#[template(in_doc = true, ext = "html", print = "none")]
struct ElementsTemplate {
artist_names: Vec<Arc<str>>,
}
/// POST `/artists`
///
/// List of artists
pub async fn elements(
State(state): State<Arc<AppState>>,
Form(form): Form<ArtistListForm>,
) -> impl IntoResponse {
debug!(search = form.search, "POST /artists");

let artists = state.artists.read().await;

let lowercase_search = form.search.to_lowercase();

let artist_names = artists
.iter()
.filter(|artist| artist.to_lowercase().contains(&lowercase_search))
.cloned()
.collect();

ElementsTemplate { artist_names }
}

mod filters {
use urlencoding::encode;

pub fn encodeurl(name: &str) -> rinja::Result<String> {
// bc of artists like AC/DC
Ok(encode(name).to_string())
}
}
58 changes: 58 additions & 0 deletions endsong_web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
pub mod artist;
pub mod artists;
pub mod r#static;

use std::sync::Arc;

use axum::{extract::State, http::StatusCode, response::IntoResponse};
use endsong::prelude::*;
use rinja::Template;
use tokio::sync::RwLock;
use tracing::debug;

/// State shared across all handlers
#[derive(Clone)]
pub struct AppState {
/// Reference to the [`SongEntries`] instance used
pub entries: Arc<RwLock<SongEntries>>,
/// Sorted list of all artist names in the dataset
pub artists: Arc<RwLock<Vec<Arc<str>>>>,
}
impl AppState {
pub fn new(entries: SongEntries) -> Arc<Self> {
Arc::new(Self {
artists: Arc::new(RwLock::new(entries.artists())),
entries: Arc::new(RwLock::new(entries)),
})
}
}

/// [`Template`] for [`not_found`]
#[derive(Template)]
#[template(path = "404.html", print = "none")]
struct NotFoundTemplate;
/// 404
pub async fn not_found() -> impl IntoResponse {
debug!("404");

(StatusCode::NOT_FOUND, NotFoundTemplate {})
}

/// [`Template`] for [`index`]
#[derive(Template)]
#[template(path = "index.html", print = "none")]
struct IndexTemplate {
total_listened: TimeDelta,
playcount: usize,
}
/// GET `/`
pub async fn index(State(state): State<Arc<AppState>>) -> impl IntoResponse {
debug!("GET /");

let entries = state.entries.read().await;

IndexTemplate {
total_listened: gather::total_listening_time(&entries),
playcount: gather::all_plays(&entries),
}
}
Loading

0 comments on commit 5988e06

Please sign in to comment.