A Wrapped / Replay like for teal.fm and rocksky.app (currently on hiatus)
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 147 lines 4.6 kB view raw
1use super::GlobalState; 2use crate::{analysis, SqliteConnection}; 3use crate::lex::queries::{ 4 GetAnnualSummaryOutput, GetAnnualSummaryRequest, GetPeriodSummaryOutput, 5 GetPeriodSummaryRequest, 6}; 7use crate::lex::{Album, Artist, Summary, TopAlbumEntry, TopArtistEntry, TopTrackEntry, Track}; 8use crate::utils::{db_call, get_public_profile, resolve_atid}; 9use axum::Json; 10use axum::extract::State; 11use axum::http::StatusCode; 12use chrono::prelude::*; 13use jacquard_axum::ExtractXrpc; 14 15pub async fn get_annual_summary( 16 State(state): State<GlobalState>, 17 ExtractXrpc(req): ExtractXrpc<GetAnnualSummaryRequest>, 18) -> Result<Json<GetAnnualSummaryOutput<'static>>, StatusCode> { 19 let did = resolve_atid(req.actor).await.map_err(|err| { 20 tracing::error!("Failed to resolve handle: {err}"); 21 StatusCode::INTERNAL_SERVER_ERROR 22 })?; 23 let profile = get_public_profile(&did).await.map_err(|err| { 24 tracing::error!("Failed to load profile: {err}"); 25 StatusCode::INTERNAL_SERVER_ERROR 26 })?; 27 28 // this sucks hard, but idk if there's a nicer way of doing it 29 let start = Utc 30 .with_ymd_and_hms(req.year as i32, 1, 1, 0, 0, 0) 31 .unwrap(); 32 let end = Utc 33 .with_ymd_and_hms(req.year as i32, 12, 31, 23, 59, 59) 34 .unwrap(); 35 36 let (albums, artists, tracks) = db_call(&state.db, move |conn| { 37 let (albums, artists, tracks) = get_core_summary(&conn, &did, start, end)?; 38 39 Ok((albums, artists, tracks)) 40 }) 41 .await 42 .map_err(|_err| StatusCode::INTERNAL_SERVER_ERROR)? 43 .map_err(|err| { 44 tracing::error!("Failed to load summary: {err}"); 45 StatusCode::INTERNAL_SERVER_ERROR 46 })?; 47 48 Ok(Json(GetAnnualSummaryOutput { 49 year: req.year, 50 profile, 51 summary: Summary { 52 top_artists: artists, 53 top_albums: albums, 54 top_tracks: tracks, 55 }, 56 extra_data: None, 57 })) 58} 59 60pub async fn get_period_summary( 61 State(state): State<GlobalState>, 62 ExtractXrpc(req): ExtractXrpc<GetPeriodSummaryRequest>, 63) -> Result<Json<GetPeriodSummaryOutput<'static>>, StatusCode> { 64 let did = resolve_atid(req.actor).await.map_err(|err| { 65 tracing::error!("Failed to resolve handle: {err}"); 66 StatusCode::INTERNAL_SERVER_ERROR 67 })?; 68 69 let start = req.start.as_ref().to_utc(); 70 let end = req.end.as_ref().to_utc(); 71 72 let profile = get_public_profile(&did).await.map_err(|err| { 73 tracing::error!("Failed to load profile: {err}"); 74 StatusCode::INTERNAL_SERVER_ERROR 75 })?; 76 77 let (albums, artists, tracks) = db_call(&state.db, move |conn| { 78 let (albums, artists, tracks) = get_core_summary(&conn, &did, start, end)?; 79 80 Ok((albums, artists, tracks)) 81 }) 82 .await 83 .map_err(|_err| StatusCode::INTERNAL_SERVER_ERROR)? 84 .map_err(|err| { 85 tracing::error!("Failed to load summary: {err}"); 86 StatusCode::INTERNAL_SERVER_ERROR 87 })?; 88 89 Ok(Json(GetPeriodSummaryOutput { 90 start: req.start, 91 end: req.end, 92 profile, 93 summary: Summary { 94 top_artists: artists, 95 top_albums: albums, 96 top_tracks: tracks, 97 }, 98 })) 99} 100 101fn get_core_summary( 102 conn: &SqliteConnection, 103 did: &str, 104 start: DateTime<Utc>, 105 end: DateTime<Utc>, 106) -> rusqlite::Result<( 107 Vec<TopAlbumEntry<'static>>, 108 Vec<TopArtistEntry<'static>>, 109 Vec<TopTrackEntry<'static>>, 110)> { 111 let albums = analysis::get_top_albums(conn, did, start, end, 10)? 112 .into_iter() 113 .map(|album| TopAlbumEntry { 114 album: Album { 115 album_name: album.name.into(), 116 album_mbid: Some(album.mbid.into()), 117 album_art_uri: None, 118 }, 119 count: album.count, 120 }) 121 .collect(); 122 123 let artists = analysis::get_top_artists(conn, did, start, end, 10)? 124 .into_iter() 125 .map(|artist| TopArtistEntry { 126 artist: Artist { 127 artist_name: artist.name.into(), 128 artist_mbid: Some(artist.mbid.into()), 129 artist_art_uri: None, 130 }, 131 count: artist.count, 132 }) 133 .collect(); 134 135 let tracks = analysis::get_top_tracks(conn, did, start, end, 10)? 136 .into_iter() 137 .map(|track| TopTrackEntry { 138 track: Track { 139 track_name: track.name.into(), 140 track_mbid: Some(track.mbid.into()), 141 }, 142 count: track.count, 143 }) 144 .collect(); 145 146 Ok((albums, artists, tracks)) 147}