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