A decentralized music tracking and discovery platform built on AT Protocol 馃幍
at setup-tracing 465 lines 15 kB view raw
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([&params.user_did, &params.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([&params.user_did, &params.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([&params.user_did, &params.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([&params.user_did, &params.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([&params.user_did, &params.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([&params.album_id, &params.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 [&params.artist_id, &params.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([&params.track_id, &params.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}