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::scrobble::{GetScrobblesParams, ScrobbleTrack};
4use actix_web::{web, HttpRequest, HttpResponse};
5use anyhow::Error;
6use duckdb::Connection;
7use tokio_stream::StreamExt;
8
9use crate::read_payload;
10
11pub async fn get_scrobbles(
12 payload: &mut web::Payload,
13 _req: &HttpRequest,
14 conn: Arc<Mutex<Connection>>,
15) -> Result<HttpResponse, Error> {
16 let body = read_payload!(payload);
17 let params = serde_json::from_slice::<GetScrobblesParams>(&body)?;
18 let pagination = params.pagination.unwrap_or_default();
19 let offset = pagination.skip.unwrap_or(0);
20 let limit = pagination.take.unwrap_or(20);
21 let did = params.user_did;
22 tracing::info!(limit, offset, user_did = ?did, "Get scrobbles");
23
24 let conn = conn.lock().unwrap();
25 let mut stmt = match did {
26 Some(_) => conn.prepare(r#"
27 SELECT
28 s.id,
29 t.id as track_id,
30 t.title,
31 t.artist,
32 t.album_artist,
33 t.album,
34 t.album_art,
35 u.handle,
36 u.did,
37 s.uri,
38 t.uri as track_uri,
39 a.uri as artist_uri,
40 al.uri as album_uri,
41 s.created_at
42 FROM scrobbles s
43 LEFT JOIN artists a ON s.artist_id = a.id
44 LEFT JOIN albums al ON s.album_id = al.id
45 LEFT JOIN tracks t ON s.track_id = t.id
46 LEFT JOIN users u ON s.user_id = u.id
47 WHERE u.did = ? OR u.handle = ?
48 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, u.did, a.uri, al.uri, s.created_at
49 ORDER BY s.created_at DESC
50 OFFSET ?
51 LIMIT ?;
52 "#)?,
53 None => conn.prepare(r#"
54 SELECT
55 s.id,
56 t.id as track_id,
57 t.title,
58 t.artist,
59 t.album_artist,
60 t.album,
61 t.album_art,
62 u.handle,
63 u.did,
64 s.uri,
65 t.uri as track_uri,
66 a.uri as artist_uri,
67 al.uri as album_uri,
68 s.created_at
69 FROM scrobbles s
70 LEFT JOIN artists a ON s.artist_id = a.id
71 LEFT JOIN albums al ON s.album_id = al.id
72 LEFT JOIN tracks t ON s.track_id = t.id
73 LEFT JOIN users u ON s.user_id = u.id
74 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, u.did, a.uri, al.uri, s.created_at
75 ORDER BY s.created_at DESC
76 OFFSET ?
77 LIMIT ?;
78 "#)?,
79 };
80 match did {
81 Some(did) => {
82 let scrobbles = stmt.query_map(
83 [&did, &did, &limit.to_string(), &offset.to_string()],
84 |row| {
85 Ok(ScrobbleTrack {
86 id: row.get(0)?,
87 track_id: row.get(1)?,
88 title: row.get(2)?,
89 artist: row.get(3)?,
90 album_artist: row.get(4)?,
91 album: row.get(5)?,
92 album_art: row.get(6)?,
93 handle: row.get(7)?,
94 did: row.get(8)?,
95 avatar: None,
96 uri: row.get(9)?,
97 track_uri: row.get(10)?,
98 artist_uri: row.get(11)?,
99 album_uri: row.get(12)?,
100 created_at: row.get(13)?,
101 })
102 },
103 )?;
104 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
105 Ok(HttpResponse::Ok().json(scrobbles?))
106 }
107 None => {
108 let scrobbles = stmt.query_map([limit, offset], |row| {
109 Ok(ScrobbleTrack {
110 id: row.get(0)?,
111 track_id: row.get(1)?,
112 title: row.get(2)?,
113 artist: row.get(3)?,
114 album_artist: row.get(4)?,
115 album: row.get(5)?,
116 album_art: row.get(6)?,
117 handle: row.get(7)?,
118 did: row.get(8)?,
119 avatar: None,
120 uri: row.get(9)?,
121 track_uri: row.get(10)?,
122 artist_uri: row.get(11)?,
123 album_uri: row.get(12)?,
124 created_at: row.get(13)?,
125 })
126 })?;
127 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
128 Ok(HttpResponse::Ok().json(scrobbles?))
129 }
130 }
131}
132
133pub async fn get_distinct_scrobbles(
134 payload: &mut web::Payload,
135 _req: &HttpRequest,
136 conn: Arc<Mutex<Connection>>,
137) -> Result<HttpResponse, Error> {
138 let body = read_payload!(payload);
139 let params = serde_json::from_slice::<GetScrobblesParams>(&body)?;
140 let pagination = params.pagination.unwrap_or_default();
141 let offset = pagination.skip.unwrap_or(0);
142 let limit = pagination.take.unwrap_or(10);
143 tracing::info!(limit, offset, user_did = ?params.user_did, "Get distinct scrobbles");
144
145 let conn = conn.lock().unwrap();
146 let mut stmt = conn.prepare(
147 r#"
148 WITH ranked_scrobbles AS (
149 SELECT
150 s.id,
151 t.id AS track_id,
152 t.title,
153 t.artist,
154 t.album_artist,
155 t.album,
156 t.album_art,
157 u.handle,
158 u.did,
159 u.avatar,
160 s.uri,
161 t.uri AS track_uri,
162 a.uri AS artist_uri,
163 al.uri AS album_uri,
164 s.created_at,
165 ROW_NUMBER() OVER (PARTITION BY u.id ORDER BY s.created_at DESC
166 ) AS rn
167 FROM scrobbles s
168 LEFT JOIN artists a ON s.artist_id = a.id
169 LEFT JOIN albums al ON s.album_id = al.id
170 LEFT JOIN tracks t ON s.track_id = t.id
171 LEFT JOIN users u ON s.user_id = u.id
172 )
173 SELECT
174 id,
175 track_id,
176 title,
177 artist,
178 album_artist,
179 album,
180 album_art,
181 handle,
182 did,
183 avatar,
184 uri,
185 track_uri,
186 artist_uri,
187 album_uri,
188 created_at
189 FROM ranked_scrobbles
190 WHERE rn = 1
191 ORDER BY created_at DESC
192 OFFSET ?
193 LIMIT ?;
194 "#,
195 )?;
196
197 let scrobbles = stmt.query_map([limit, offset], |row| {
198 Ok(ScrobbleTrack {
199 id: row.get(0)?,
200 track_id: row.get(1)?,
201 title: row.get(2)?,
202 artist: row.get(3)?,
203 album_artist: row.get(4)?,
204 album: row.get(5)?,
205 album_art: row.get(6)?,
206 handle: row.get(7)?,
207 did: row.get(8)?,
208 avatar: row.get(9)?,
209 uri: row.get(10)?,
210 track_uri: row.get(11)?,
211 artist_uri: row.get(12)?,
212 album_uri: row.get(13)?,
213 created_at: row.get(14)?,
214 })
215 })?;
216 let scrobbles: Result<Vec<_>, _> = scrobbles.collect();
217 Ok(HttpResponse::Ok().json(scrobbles?))
218}