use super::GlobalState; use crate::{analysis, SqliteConnection}; use crate::lex::queries::{ GetAnnualSummaryOutput, GetAnnualSummaryRequest, GetPeriodSummaryOutput, GetPeriodSummaryRequest, }; use crate::lex::{Album, Artist, Summary, TopAlbumEntry, TopArtistEntry, TopTrackEntry, Track}; use crate::utils::{db_call, get_public_profile, resolve_atid}; use axum::Json; use axum::extract::State; use axum::http::StatusCode; use chrono::prelude::*; use jacquard_axum::ExtractXrpc; pub async fn get_annual_summary( State(state): State, ExtractXrpc(req): ExtractXrpc, ) -> Result>, StatusCode> { let did = resolve_atid(req.actor).await.map_err(|err| { tracing::error!("Failed to resolve handle: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; let profile = get_public_profile(&did).await.map_err(|err| { tracing::error!("Failed to load profile: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; // this sucks hard, but idk if there's a nicer way of doing it let start = Utc .with_ymd_and_hms(req.year as i32, 1, 1, 0, 0, 0) .unwrap(); let end = Utc .with_ymd_and_hms(req.year as i32, 12, 31, 23, 59, 59) .unwrap(); let (albums, artists, tracks) = db_call(&state.db, move |conn| { let (albums, artists, tracks) = get_core_summary(&conn, &did, start, end)?; Ok((albums, artists, tracks)) }) .await .map_err(|_err| StatusCode::INTERNAL_SERVER_ERROR)? .map_err(|err| { tracing::error!("Failed to load summary: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; Ok(Json(GetAnnualSummaryOutput { year: req.year, profile, summary: Summary { top_artists: artists, top_albums: albums, top_tracks: tracks, }, extra_data: None, })) } pub async fn get_period_summary( State(state): State, ExtractXrpc(req): ExtractXrpc, ) -> Result>, StatusCode> { let did = resolve_atid(req.actor).await.map_err(|err| { tracing::error!("Failed to resolve handle: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; let start = req.start.as_ref().to_utc(); let end = req.end.as_ref().to_utc(); let profile = get_public_profile(&did).await.map_err(|err| { tracing::error!("Failed to load profile: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; let (albums, artists, tracks) = db_call(&state.db, move |conn| { let (albums, artists, tracks) = get_core_summary(&conn, &did, start, end)?; Ok((albums, artists, tracks)) }) .await .map_err(|_err| StatusCode::INTERNAL_SERVER_ERROR)? .map_err(|err| { tracing::error!("Failed to load summary: {err}"); StatusCode::INTERNAL_SERVER_ERROR })?; Ok(Json(GetPeriodSummaryOutput { start: req.start, end: req.end, profile, summary: Summary { top_artists: artists, top_albums: albums, top_tracks: tracks, }, })) } fn get_core_summary( conn: &SqliteConnection, did: &str, start: DateTime, end: DateTime, ) -> rusqlite::Result<( Vec>, Vec>, Vec>, )> { let albums = analysis::get_top_albums(conn, did, start, end, 10)? .into_iter() .map(|album| TopAlbumEntry { album: Album { album_name: album.name.into(), album_mbid: Some(album.mbid.into()), album_art_uri: None, }, count: album.count, }) .collect(); let artists = analysis::get_top_artists(conn, did, start, end, 10)? .into_iter() .map(|artist| TopArtistEntry { artist: Artist { artist_name: artist.name.into(), artist_mbid: Some(artist.mbid.into()), artist_art_uri: None, }, count: artist.count, }) .collect(); let tracks = analysis::get_top_tracks(conn, did, start, end, 10)? .into_iter() .map(|track| TopTrackEntry { track: Track { track_name: track.name.into(), track_mbid: Some(track.mbid.into()), }, count: track.count, }) .collect(); Ok((albums, artists, tracks)) }