forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1use std::sync::{Arc, Mutex};
2
3use crate::read_payload;
4use crate::types::{
5 scrobble::{ScrobblesPerDay, ScrobblesPerMonth, ScrobblesPerYear},
6 stats::{
7 GetAlbumScrobblesParams, GetArtistScrobblesParams, GetScrobblesPerDayParams,
8 GetScrobblesPerMonthParams, GetScrobblesPerYearParams, GetStatsParams,
9 GetTrackScrobblesParams,
10 },
11};
12use actix_web::{web, HttpRequest, HttpResponse};
13use anyhow::Error;
14use duckdb::Connection;
15use serde_json::json;
16use tokio_stream::StreamExt;
17
18pub async fn get_stats(
19 payload: &mut web::Payload,
20 _req: &HttpRequest,
21 conn: Arc<Mutex<Connection>>,
22) -> Result<HttpResponse, Error> {
23 let body = read_payload!(payload);
24
25 let params = serde_json::from_slice::<GetStatsParams>(&body)?;
26 tracing::info!(user_did = ?params.user_did, "Get stats");
27
28 let conn = conn.lock().unwrap();
29 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 = ?")?;
30 let scrobbles: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
31
32 let mut stmt = conn.prepare(
33 r#"
34 SELECT COUNT(*) FROM (
35 SELECT
36 s.artist_id AS id,
37 ar.name AS artist_name,
38 ar.picture AS picture,
39 ar.sha256 AS sha256,
40 ar.uri AS uri,
41 COUNT(*) AS play_count,
42 COUNT(DISTINCT s.user_id) AS unique_listeners
43 FROM
44 scrobbles s
45 LEFT JOIN
46 artists ar ON s.artist_id = ar.id
47 LEFT JOIN
48 users u ON s.user_id = u.id
49 WHERE
50 s.artist_id IS NOT NULL AND (u.did = ? OR u.handle = ?)
51 GROUP BY
52 s.artist_id, ar.name, ar.uri, ar.picture, ar.sha256
53 )
54 "#,
55 )?;
56 let artists: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
57
58 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 = ?")?;
59 let loved_tracks: i64 =
60 stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
61
62 let mut stmt = conn.prepare(r#"SELECT COUNT(*) FROM (
63 SELECT
64 s.album_id AS id,
65 a.title AS title,
66 ar.name AS artist,
67 ar.uri AS artist_uri,
68 a.album_art AS album_art,
69 a.release_date,
70 a.year,
71 a.uri,
72 a.sha256,
73 COUNT(*) AS play_count,
74 COUNT(DISTINCT s.user_id) AS unique_listeners
75 FROM
76 scrobbles s
77 LEFT JOIN
78 albums a ON s.album_id = a.id
79 LEFT JOIN
80 artists ar ON a.artist = ar.name
81 LEFT JOIN
82 users u ON s.user_id = u.id
83 WHERE s.album_id IS NOT NULL AND (u.did = ? OR u.handle = ?)
84 GROUP BY
85 s.album_id, a.title, ar.name, a.release_date, a.year, a.uri, a.album_art, a.sha256, ar.uri
86 )"#)?;
87 let albums: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
88
89 let mut stmt = conn.prepare(
90 r#"
91 SELECT COUNT(*) FROM tracks t
92 LEFT JOIN user_tracks ut ON ut.track_id = t.id
93 LEFT JOIN users u ON ut.user_id = u.id
94 WHERE u.did = ? OR u.handle = ?
95 "#,
96 )?;
97 let tracks: i64 = stmt.query_row([¶ms.user_did, ¶ms.user_did], |row| row.get(0))?;
98
99 Ok(HttpResponse::Ok().json(json!({
100 "scrobbles": scrobbles,
101 "artists": artists,
102 "loved_tracks": loved_tracks,
103 "albums": albums,
104 "tracks": tracks,
105 })))
106}
107
108pub async fn get_scrobbles_per_day(
109 payload: &mut web::Payload,
110 _req: &HttpRequest,
111 conn: Arc<Mutex<Connection>>,
112) -> Result<HttpResponse, Error> {
113 let body = read_payload!(payload);
114 let params = serde_json::from_slice::<GetScrobblesPerDayParams>(&body)?;
115 let start = params
116 .start
117 .unwrap_or(GetScrobblesPerDayParams::default().start.unwrap());
118 let end = params
119 .end
120 .unwrap_or(GetScrobblesPerDayParams::default().end.unwrap());
121 let did = params.user_did;
122 tracing::info!(start = %start, end = %end, user_did = ?did, "Get scrobbles per day");
123
124 let conn = conn.lock().unwrap();
125 match did {
126 Some(did) => {
127 let mut stmt = conn.prepare(
128 r#"
129 SELECT
130 date_trunc('day', created_at) AS date,
131 COUNT(track_id) AS count
132 FROM
133 scrobbles
134 LEFT JOIN users u ON scrobbles.user_id = u.id
135 WHERE
136 u.did = ? OR u.handle = ?
137 AND created_at BETWEEN ? AND ?
138 GROUP BY
139 date_trunc('day', created_at)
140 ORDER BY
141 date;
142 "#,
143 )?;
144 let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
145 Ok(ScrobblesPerDay {
146 date: row.get(0)?,
147 count: row.get(1)?,
148 })
149 })?;
150 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
151 Ok(HttpResponse::Ok().json(scrobbles?))
152 }
153 None => {
154 let mut stmt = conn.prepare(
155 r#"
156 SELECT
157 date_trunc('day', created_at) AS date,
158 COUNT(track_id) AS count
159 FROM
160 scrobbles
161 WHERE
162 created_at BETWEEN ? AND ?
163 GROUP BY
164 date_trunc('day', created_at)
165 ORDER BY
166 date;
167 "#,
168 )?;
169 let scrobbles = stmt.query_map([start, end], |row| {
170 Ok(ScrobblesPerDay {
171 date: row.get(0)?,
172 count: row.get(1)?,
173 })
174 })?;
175 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
176 Ok(HttpResponse::Ok().json(scrobbles?))
177 }
178 }
179}
180
181pub async fn get_scrobbles_per_month(
182 payload: &mut web::Payload,
183 _req: &HttpRequest,
184 conn: Arc<Mutex<Connection>>,
185) -> Result<HttpResponse, Error> {
186 let body = read_payload!(payload);
187 let params = serde_json::from_slice::<GetScrobblesPerMonthParams>(&body)?;
188 let start = params
189 .start
190 .unwrap_or(GetScrobblesPerDayParams::default().start.unwrap());
191 let end = params
192 .end
193 .unwrap_or(GetScrobblesPerDayParams::default().end.unwrap());
194 let did = params.user_did;
195 tracing::info!(start = %start, end = %end, user_did = ?did, "Get scrobbles per month");
196
197 let conn = conn.lock().unwrap();
198 match did {
199 Some(did) => {
200 let mut stmt = conn.prepare(
201 r#"
202 SELECT
203 EXTRACT(YEAR FROM created_at) || '-' ||
204 LPAD(EXTRACT(MONTH FROM created_at)::VARCHAR, 2, '0') AS year_month,
205 COUNT(*) AS count
206 FROM
207 scrobbles
208 LEFT JOIN users u ON scrobbles.user_id = u.id
209 WHERE
210 u.did = ? OR u.handle = ?
211 AND created_at BETWEEN ? AND ?
212 GROUP BY
213 EXTRACT(YEAR FROM created_at),
214 EXTRACT(MONTH FROM created_at)
215 ORDER BY
216 year_month;
217 "#,
218 )?;
219 let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
220 Ok(ScrobblesPerMonth {
221 year_month: row.get(0)?,
222 count: row.get(1)?,
223 })
224 })?;
225 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
226 Ok(HttpResponse::Ok().json(scrobbles?))
227 }
228 None => {
229 let mut stmt = conn.prepare(
230 r#"
231 SELECT
232 EXTRACT(YEAR FROM created_at) || '-' ||
233 LPAD(EXTRACT(MONTH FROM created_at)::VARCHAR, 2, '0') AS year_month,
234 COUNT(*) AS count
235 FROM
236 scrobbles
237 WHERE
238 created_at BETWEEN ? AND ?
239 GROUP BY
240 EXTRACT(YEAR FROM created_at),
241 EXTRACT(MONTH FROM created_at)
242 ORDER BY
243 year_month;
244 "#,
245 )?;
246 let scrobbles = stmt.query_map([start, end], |row| {
247 Ok(ScrobblesPerMonth {
248 year_month: row.get(0)?,
249 count: row.get(1)?,
250 })
251 })?;
252 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
253 Ok(HttpResponse::Ok().json(scrobbles?))
254 }
255 }
256}
257
258pub async fn get_scrobbles_per_year(
259 payload: &mut web::Payload,
260 _req: &HttpRequest,
261 conn: Arc<Mutex<Connection>>,
262) -> Result<HttpResponse, Error> {
263 let body = read_payload!(payload);
264 let params = serde_json::from_slice::<GetScrobblesPerYearParams>(&body)?;
265 let start = params
266 .start
267 .unwrap_or(GetScrobblesPerDayParams::default().start.unwrap());
268 let end = params
269 .end
270 .unwrap_or(GetScrobblesPerDayParams::default().end.unwrap());
271 let did = params.user_did;
272 tracing::info!(start = %start, end = %end, user_did = ?did, "Get scrobbles per year");
273
274 let conn = conn.lock().unwrap();
275 match did {
276 Some(did) => {
277 let mut stmt = conn.prepare(
278 r#"
279 SELECT
280 EXTRACT(YEAR FROM created_at) AS year,
281 COUNT(*) AS count
282 FROM
283 scrobbles
284 LEFT JOIN users u ON scrobbles.user_id = u.id
285 WHERE
286 u.did = ? OR u.handle = ?
287 AND created_at BETWEEN ? AND ?
288 GROUP BY
289 EXTRACT(YEAR FROM created_at)
290 ORDER BY
291 year;
292 "#,
293 )?;
294 let scrobbles = stmt.query_map([&did, &did, &start, &end], |row| {
295 Ok(ScrobblesPerYear {
296 year: row.get(0)?,
297 count: row.get(1)?,
298 })
299 })?;
300 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
301 Ok(HttpResponse::Ok().json(scrobbles?))
302 }
303 None => {
304 let mut stmt = conn.prepare(
305 r#"
306 SELECT
307 EXTRACT(YEAR FROM created_at) AS year,
308 COUNT(*) AS count
309 FROM
310 scrobbles
311 WHERE
312 created_at BETWEEN ? AND ?
313 GROUP BY
314 EXTRACT(YEAR FROM created_at)
315 ORDER BY
316 year;
317 "#,
318 )?;
319 let scrobbles = stmt.query_map([start, end], |row| {
320 Ok(ScrobblesPerYear {
321 year: row.get(0)?,
322 count: row.get(1)?,
323 })
324 })?;
325 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
326 Ok(HttpResponse::Ok().json(scrobbles?))
327 }
328 }
329}
330
331pub async fn get_album_scrobbles(
332 payload: &mut web::Payload,
333 _req: &HttpRequest,
334 conn: Arc<Mutex<Connection>>,
335) -> Result<HttpResponse, Error> {
336 let body = read_payload!(payload);
337 let params = serde_json::from_slice::<GetAlbumScrobblesParams>(&body)?;
338 let start = params
339 .start
340 .unwrap_or(GetAlbumScrobblesParams::default().start.unwrap());
341 let end = params
342 .end
343 .unwrap_or(GetAlbumScrobblesParams::default().end.unwrap());
344 let conn = conn.lock().unwrap();
345 tracing::info!(album_id = %params.album_id, start = %start, end = %end, "Get album scrobbles");
346
347 let mut stmt = conn.prepare(
348 r#"
349 SELECT
350 date_trunc('day', s.created_at) AS date,
351 COUNT(s.album_id) AS count
352 FROM
353 scrobbles s
354 LEFT JOIN albums a ON s.album_id = a.id
355 WHERE
356 a.id = ? OR a.uri = ?
357 AND s.created_at BETWEEN ? AND ?
358 GROUP BY
359 date_trunc('day', s.created_at)
360 ORDER BY
361 date;
362 "#,
363 )?;
364 let scrobbles = stmt.query_map([¶ms.album_id, ¶ms.album_id, &start, &end], |row| {
365 Ok(ScrobblesPerDay {
366 date: row.get(0)?,
367 count: row.get(1)?,
368 })
369 })?;
370 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
371 Ok(HttpResponse::Ok().json(scrobbles?))
372}
373
374pub async fn get_artist_scrobbles(
375 payload: &mut web::Payload,
376 _req: &HttpRequest,
377 conn: Arc<Mutex<Connection>>,
378) -> Result<HttpResponse, Error> {
379 let body = read_payload!(payload);
380 let params = serde_json::from_slice::<GetArtistScrobblesParams>(&body)?;
381 let start = params
382 .start
383 .unwrap_or(GetArtistScrobblesParams::default().start.unwrap());
384 let end = params
385 .end
386 .unwrap_or(GetArtistScrobblesParams::default().end.unwrap());
387 let conn = conn.lock().unwrap();
388 tracing::info!(artist_id = %params.artist_id, start = %start, end = %end, "Get artist scrobbles");
389
390 let mut stmt = conn.prepare(
391 r#"
392 SELECT
393 date_trunc('day', s.created_at) AS date,
394 COUNT(s.artist_id) AS count
395 FROM
396 scrobbles s
397 LEFT JOIN artists a ON s.artist_id = a.id
398 WHERE
399 a.id = ? OR a.uri = ?
400 AND s.created_at BETWEEN ? AND ?
401 GROUP BY
402 date_trunc('day', s.created_at)
403 ORDER BY
404 date;
405 "#,
406 )?;
407
408 let scrobbles = stmt.query_map(
409 [¶ms.artist_id, ¶ms.artist_id, &start, &end],
410 |row| {
411 Ok(ScrobblesPerDay {
412 date: row.get(0)?,
413 count: row.get(1)?,
414 })
415 },
416 )?;
417
418 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
419 Ok(HttpResponse::Ok().json(scrobbles?))
420}
421
422pub async fn get_track_scrobbles(
423 payload: &mut web::Payload,
424 _req: &HttpRequest,
425 conn: Arc<Mutex<Connection>>,
426) -> Result<HttpResponse, Error> {
427 let body = read_payload!(payload);
428 let params = serde_json::from_slice::<GetTrackScrobblesParams>(&body)?;
429 let start = params
430 .start
431 .unwrap_or(GetTrackScrobblesParams::default().start.unwrap());
432 let end = params
433 .end
434 .unwrap_or(GetTrackScrobblesParams::default().end.unwrap());
435 let conn = conn.lock().unwrap();
436 tracing::info!(track_id = %params.track_id, start = %start, end = %end, "Get track scrobbles");
437
438 let mut stmt = conn.prepare(
439 r#"
440 SELECT
441 date_trunc('day', s.created_at) AS date,
442 COUNT(s.track_id) AS count
443 FROM
444 scrobbles s
445 LEFT JOIN tracks t ON s.track_id = t.id
446 WHERE
447 t.id = ? OR t.uri = ?
448 AND s.created_at BETWEEN ? AND ?
449 GROUP BY
450 date_trunc('day', s.created_at)
451 ORDER BY
452 date;
453 "#,
454 )?;
455
456 let scrobbles = stmt.query_map([¶ms.track_id, ¶ms.track_id, &start, &end], |row| {
457 Ok(ScrobblesPerDay {
458 date: row.get(0)?,
459 count: row.get(1)?,
460 })
461 })?;
462
463 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
464 Ok(HttpResponse::Ok().json(scrobbles?))
465}