···4use analytics::types::scrobble::{GetScrobblesParams, ScrobbleTrack};
5use duckdb::Connection;
6use anyhow::Error;
7-use futures_util::StreamExt;
89use crate::read_payload;
10···38 LEFT JOIN albums al ON s.album_id = al.id
39 LEFT JOIN tracks t ON s.track_id = t.id
40 LEFT JOIN users u ON s.user_id = u.id
41- WHERE u.did = ?
42 GROUP BY s.id, s.created_at, t.id, t.title, t.artist, t.album_artist, t.album, t.album_art, s.uri, t.uri, u.handle, a.uri, al.uri, s.created_at
43 ORDER BY s.created_at DESC
44 OFFSET ?
···72 };
73 match did {
74 Some(did) => {
75- let scrobbles = stmt.query_map([did, limit.to_string(), offset.to_string()], |row| {
76 Ok(ScrobbleTrack {
77 id: row.get(0)?,
78 track_id: row.get(1)?,
···4use analytics::types::scrobble::{GetScrobblesParams, ScrobbleTrack};
5use duckdb::Connection;
6use anyhow::Error;
7+use tokio_stream::StreamExt;
89use crate::read_payload;
10···38 LEFT JOIN albums al ON s.album_id = al.id
39 LEFT JOIN tracks t ON s.track_id = t.id
40 LEFT JOIN users u ON s.user_id = u.id
41+ WHERE u.did = ? OR u.handle = ?
42 GROUP BY s.id, s.created_at, t.id, t.title, t.artist, t.album_artist, t.album, t.album_art, s.uri, t.uri, u.handle, a.uri, al.uri, s.created_at
43 ORDER BY s.created_at DESC
44 OFFSET ?
···72 };
73 match did {
74 Some(did) => {
75+ let scrobbles = stmt.query_map([&did, &did, &limit.to_string(), &offset.to_string()], |row| {
76 Ok(ScrobbleTrack {
77 id: row.get(0)?,
78 track_id: row.get(1)?,
+132-18
crates/analytics/src/handlers/stats.rs
···1use std::sync::{Arc, Mutex};
23use actix_web::{web, HttpRequest, HttpResponse};
4-use analytics::types::{scrobble::{ScrobblesPerDay, ScrobblesPerMonth, ScrobblesPerYear}, stats::{GetScrobblesPerDayParams, GetScrobblesPerMonthParams, GetScrobblesPerYearParams, GetStatsParams}};
5use duckdb::Connection;
6use anyhow::Error;
7use serde_json::json;
8-use futures_util::StreamExt;
9use crate::read_payload;
1011pub async fn get_stats(payload: &mut web::Payload, _req: &HttpRequest, conn: Arc<Mutex<Connection>>) -> Result<HttpResponse, Error> {
···13 let params = serde_json::from_slice::<GetStatsParams>(&body)?;
1415 let conn = conn.lock().unwrap();
16- let mut stmt = conn.prepare("SELECT COUNT(*) FROM scrobbles s LEFT JOIN users u ON s.user_id = u.id WHERE u.did = ?")?;
17- let scrobbles: i64 = stmt.query_row([¶ms.user_did], |row| row.get(0))?;
1819- let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_artists LEFT JOIN users u ON user_artists.user_id = u.id WHERE u.did = ?")?;
20- let artists: i64 = stmt.query_row([¶ms.user_did], |row| row.get(0))?;
2122- let mut stmt = conn.prepare("SELECT COUNT(*) FROM loved_tracks LEFT JOIN users u ON loved_tracks.user_id = u.id WHERE u.did = ?")?;
23- let loved_tracks: i64 = stmt.query_row([¶ms.user_did], |row| row.get(0))?;
2425- let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_albums LEFT JOIN users u ON user_albums.user_id = u.id WHERE u.did = ?")?;
26- let albums: i64 = stmt.query_row([¶ms.user_did], |row| row.get(0))?;
2728- let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_tracks LEFT JOIN users u ON user_tracks.user_id = u.id WHERE u.did = ?")?;
29- let tracks: i64 = stmt.query_row([¶ms.user_did], |row| row.get(0))?;
3031 Ok(HttpResponse::Ok().json(json!({
32 "scrobbles": scrobbles,
···55 scrobbles
56 LEFT JOIN users u ON scrobbles.user_id = u.id
57 WHERE
58- u.did = ?
59 AND created_at BETWEEN ? AND ?
60 GROUP BY
61 date_trunc('day', created_at)
62 ORDER BY
63 date;
64 "#)?;
65- let scrobbles = stmt.query_map([did, start, end], |row| {
66 Ok(ScrobblesPerDay {
67 date: row.get(0)?,
68 count: row.get(1)?,
···116 scrobbles
117 LEFT JOIN users u ON scrobbles.user_id = u.id
118 WHERE
119- u.did = ?
120 AND created_at BETWEEN ? AND ?
121 GROUP BY
122 EXTRACT(YEAR FROM created_at),
···124 ORDER BY
125 year_month;
126 "#)?;
127- let scrobbles = stmt.query_map([did, start, end], |row| {
128 Ok(ScrobblesPerMonth {
129 year_month: row.get(0)?,
130 count: row.get(1)?,
···179 scrobbles
180 LEFT JOIN users u ON scrobbles.user_id = u.id
181 WHERE
182- u.did = ?
183 AND created_at BETWEEN ? AND ?
184 GROUP BY
185 EXTRACT(YEAR FROM created_at)
186 ORDER BY
187 year;
188 "#)?;
189- let scrobbles = stmt.query_map([did, start, end], |row| {
190 Ok(ScrobblesPerYear {
191 year: row.get(0)?,
192 count: row.get(1)?,
···220 }
221 }
222}
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
···1use std::sync::{Arc, Mutex};
23use actix_web::{web, HttpRequest, HttpResponse};
4+use analytics::types::{scrobble::{ScrobblesPerDay, ScrobblesPerMonth, ScrobblesPerYear}, stats::{GetAlbumScrobblesParams, GetArtistScrobblesParams, GetScrobblesPerDayParams, GetScrobblesPerMonthParams, GetScrobblesPerYearParams, GetStatsParams, GetTrackScrobblesParams}};
5use duckdb::Connection;
6use anyhow::Error;
7use serde_json::json;
8+use tokio_stream::StreamExt;
9use crate::read_payload;
1011pub async fn get_stats(payload: &mut web::Payload, _req: &HttpRequest, conn: Arc<Mutex<Connection>>) -> Result<HttpResponse, Error> {
···13 let params = serde_json::from_slice::<GetStatsParams>(&body)?;
1415 let conn = conn.lock().unwrap();
16+ let mut stmt = conn.prepare("SELECT COUNT(*) FROM scrobbles s LEFT JOIN users u ON s.user_id = u.id WHERE u.did = ? OR u.handle = ?")?;
17+ let scrobbles: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
1819+ let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_artists LEFT JOIN users u ON user_artists.user_id = u.id WHERE u.did = ? OR u.handle = ?")?;
20+ let artists: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
2122+ let mut stmt = conn.prepare("SELECT COUNT(*) FROM loved_tracks LEFT JOIN users u ON loved_tracks.user_id = u.id WHERE u.did = ? OR u.handle = ?")?;
23+ let loved_tracks: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
2425+ let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_albums LEFT JOIN users u ON user_albums.user_id = u.id WHERE u.did = ? OR u.handle = ?")?;
26+ let albums: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
2728+ let mut stmt = conn.prepare("SELECT COUNT(*) FROM user_tracks LEFT JOIN users u ON user_tracks.user_id = u.id WHERE u.did = ? OR u.handle = ?")?;
29+ let tracks: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
3031 Ok(HttpResponse::Ok().json(json!({
32 "scrobbles": scrobbles,
···55 scrobbles
56 LEFT JOIN users u ON scrobbles.user_id = u.id
57 WHERE
58+ u.did = ? OR u.handle = ?
59 AND created_at BETWEEN ? AND ?
60 GROUP BY
61 date_trunc('day', created_at)
62 ORDER BY
63 date;
64 "#)?;
65+ let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
66 Ok(ScrobblesPerDay {
67 date: row.get(0)?,
68 count: row.get(1)?,
···116 scrobbles
117 LEFT JOIN users u ON scrobbles.user_id = u.id
118 WHERE
119+ u.did = ? OR u.handle = ?
120 AND created_at BETWEEN ? AND ?
121 GROUP BY
122 EXTRACT(YEAR FROM created_at),
···124 ORDER BY
125 year_month;
126 "#)?;
127+ let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
128 Ok(ScrobblesPerMonth {
129 year_month: row.get(0)?,
130 count: row.get(1)?,
···179 scrobbles
180 LEFT JOIN users u ON scrobbles.user_id = u.id
181 WHERE
182+ u.did = ? OR u.handle = ?
183 AND created_at BETWEEN ? AND ?
184 GROUP BY
185 EXTRACT(YEAR FROM created_at)
186 ORDER BY
187 year;
188 "#)?;
189+ let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
190 Ok(ScrobblesPerYear {
191 year: row.get(0)?,
192 count: row.get(1)?,
···220 }
221 }
222}
223+224+pub async fn get_album_scrobbles(payload: &mut web::Payload, _req: &HttpRequest, conn: Arc<Mutex<Connection>>) -> Result<HttpResponse, Error> {
225+ let body = read_payload!(payload);
226+ let params = serde_json::from_slice::<GetAlbumScrobblesParams>(&body)?;
227+ let start = params.start.unwrap_or(GetAlbumScrobblesParams::default().start.unwrap());
228+ let end = params.end.unwrap_or(GetAlbumScrobblesParams::default().end.unwrap());
229+ let conn = conn.lock().unwrap();
230+ let mut stmt = conn.prepare(r#"
231+ SELECT
232+ date_trunc('day', s.created_at) AS date,
233+ COUNT(s.album_id) AS count
234+ FROM
235+ scrobbles s
236+ LEFT JOIN albums a ON s.album_id = a.id
237+ WHERE
238+ a.id = ? OR a.uri = ?
239+ AND s.created_at BETWEEN ? AND ?
240+ GROUP BY
241+ date_trunc('day', s.created_at)
242+ ORDER BY
243+ date;
244+ "#)?;
245+ let scrobbles = stmt.query_map([
246+ ¶ms.album_id,
247+ ¶ms.album_id,
248+ &start,
249+ &end
250+ ], |row| {
251+ Ok(ScrobblesPerDay {
252+ date: row.get(0)?,
253+ count: row.get(1)?,
254+ })
255+ })?;
256+ let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
257+ Ok(HttpResponse::Ok().json(scrobbles?))
258+}
259+260+pub async fn get_artist_scrobbles(payload: &mut web::Payload, _req: &HttpRequest, conn: Arc<Mutex<Connection>>) -> Result<HttpResponse, Error> {
261+ let body = read_payload!(payload);
262+ let params = serde_json::from_slice::<GetArtistScrobblesParams>(&body)?;
263+ let start = params.start.unwrap_or(GetArtistScrobblesParams::default().start.unwrap());
264+ let end = params.end.unwrap_or(GetArtistScrobblesParams::default().end.unwrap());
265+ let conn = conn.lock().unwrap();
266+267+ let mut stmt = conn.prepare(r#"
268+ SELECT
269+ date_trunc('day', s.created_at) AS date,
270+ COUNT(s.artist_id) AS count
271+ FROM
272+ scrobbles s
273+ LEFT JOIN artists a ON s.artist_id = a.id
274+ WHERE
275+ a.id = ? OR a.uri = ?
276+ AND s.created_at BETWEEN ? AND ?
277+ GROUP BY
278+ date_trunc('day', s.created_at)
279+ ORDER BY
280+ date;
281+ "#)?;
282+283+ let scrobbles = stmt.query_map([
284+ ¶ms.artist_id,
285+ ¶ms.artist_id,
286+ &start,
287+ &end
288+ ], |row| {
289+ Ok(ScrobblesPerDay {
290+ date: row.get(0)?,
291+ count: row.get(1)?,
292+ })
293+ })?;
294+295+ let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
296+ Ok(HttpResponse::Ok().json(scrobbles?))
297+}
298+299+pub async fn get_track_scrobbles(payload: &mut web::Payload, _req: &HttpRequest, conn: Arc<Mutex<Connection>>) -> Result<HttpResponse, Error> {
300+ let body = read_payload!(payload);
301+ let params = serde_json::from_slice::<GetTrackScrobblesParams>(&body)?;
302+ let start = params.start.unwrap_or(GetTrackScrobblesParams::default().start.unwrap());
303+ let end = params.end.unwrap_or(GetTrackScrobblesParams::default().end.unwrap());
304+ let conn = conn.lock().unwrap();
305+306+ let mut stmt = conn.prepare(r#"
307+ SELECT
308+ date_trunc('day', s.created_at) AS date,
309+ COUNT(s.track_id) AS count
310+ FROM
311+ scrobbles s
312+ LEFT JOIN tracks t ON s.track_id = t.id
313+ WHERE
314+ t.id = ? OR t.uri = ?
315+ AND s.created_at BETWEEN ? AND ?
316+ GROUP BY
317+ date_trunc('day', s.created_at)
318+ ORDER BY
319+ date;
320+ "#)?;
321+322+ let scrobbles = stmt.query_map([
323+ ¶ms.track_id,
324+ ¶ms.track_id,
325+ &start,
326+ &end
327+ ], |row| {
328+ Ok(ScrobblesPerDay {
329+ date: row.get(0)?,
330+ count: row.get(1)?,
331+ })
332+ })?;
333+334+ let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
335+ Ok(HttpResponse::Ok().json(scrobbles?))
336+}
+7-7
crates/analytics/src/handlers/tracks.rs
···4use analytics::types::track::{GetLovedTracksParams, GetTopTracksParams, GetTracksParams, Track};
5use duckdb::Connection;
6use anyhow::Error;
7-use futures_util::StreamExt;
89use crate::read_payload;
10···48 FROM tracks t
49 LEFT JOIN user_tracks ut ON t.id = ut.track_id
50 LEFT JOIN users u ON ut.user_id = u.id
51- WHERE u.did = ?
52 ORDER BY t.title ASC
53 OFFSET ?
54 LIMIT ?;
55 "#)?;
56- let tracks = stmt.query_map([did, limit.to_string(), offset.to_string()], |row| {
57 Ok(Track {
58 id: row.get(0)?,
59 title: row.get(1)?,
···186 FROM loved_tracks l
187 LEFT JOIN users u ON l.user_id = u.id
188 LEFT JOIN tracks t ON l.track_id = t.id
189- WHERE u.did = ?
190 ORDER BY l.created_at DESC
191 OFFSET ?
192 LIMIT ?;
193 "#)?;
194- let loved_tracks = stmt.query_map([did, limit.to_string(), offset.to_string()], |row| {
195 Ok(Track {
196 id: row.get(0)?,
197 title: row.get(1)?,
···257 LEFT JOIN artists ar ON s.artist_id = ar.id
258 LEFT JOIN albums a ON s.album_id = a.id
259 LEFT JOIN users u ON s.user_id = u.id
260- WHERE u.did = ?
261 GROUP BY t.id, s.track_id, t.title, ar.name, a.title, t.artist, t.uri, t.album_art, t.duration, t.disc_number, t.track_number, t.artist_uri, t.album_uri, t.created_at, t.sha256, t.album_artist, t.album
262 ORDER BY play_count DESC
263 OFFSET ?
264 LIMIT ?;
265 "#)?;
266- let top_tracks = stmt.query_map([did, limit.to_string(), offset.to_string()], |row| {
267 Ok(Track {
268 id: row.get(0)?,
269 title: row.get(1)?,
···4use analytics::types::track::{GetLovedTracksParams, GetTopTracksParams, GetTracksParams, Track};
5use duckdb::Connection;
6use anyhow::Error;
7+use tokio_stream::StreamExt;
89use crate::read_payload;
10···48 FROM tracks t
49 LEFT JOIN user_tracks ut ON t.id = ut.track_id
50 LEFT JOIN users u ON ut.user_id = u.id
51+ WHERE u.did = ? OR u.handle = ?
52 ORDER BY t.title ASC
53 OFFSET ?
54 LIMIT ?;
55 "#)?;
56+ let tracks = stmt.query_map([&did, &did, &limit.to_string(), &offset.to_string()], |row| {
57 Ok(Track {
58 id: row.get(0)?,
59 title: row.get(1)?,
···186 FROM loved_tracks l
187 LEFT JOIN users u ON l.user_id = u.id
188 LEFT JOIN tracks t ON l.track_id = t.id
189+ WHERE u.did = ? OR u.handle = ?
190 ORDER BY l.created_at DESC
191 OFFSET ?
192 LIMIT ?;
193 "#)?;
194+ let loved_tracks = stmt.query_map([&did, &did, &limit.to_string(), &offset.to_string()], |row| {
195 Ok(Track {
196 id: row.get(0)?,
197 title: row.get(1)?,
···257 LEFT JOIN artists ar ON s.artist_id = ar.id
258 LEFT JOIN albums a ON s.album_id = a.id
259 LEFT JOIN users u ON s.user_id = u.id
260+ WHERE u.did = ? OR u.handle = ?
261 GROUP BY t.id, s.track_id, t.title, ar.name, a.title, t.artist, t.uri, t.album_art, t.duration, t.disc_number, t.track_number, t.artist_uri, t.album_uri, t.created_at, t.sha256, t.album_artist, t.album
262 ORDER BY play_count DESC
263 OFFSET ?
264 LIMIT ?;
265 "#)?;
266+ let top_tracks = stmt.query_map([&did, &did, &limit.to_string(), &offset.to_string()], |row| {
267 Ok(Track {
268 id: row.get(0)?,
269 title: row.get(1)?,
+1
crates/analytics/src/main.rs
···12pub mod cmd;
13pub mod core;
14pub mod handlers;
01516fn cli() -> Command {
17 Command::new("analytics")
···12pub mod cmd;
13pub mod core;
14pub mod handlers;
15+pub mod subscriber;
1617fn cli() -> Command {
18 Command::new("analytics")