1use std::time::Duration;
3
4use surrealdb::{Connection, Surreal};
5use tracing::instrument;
6
7use crate::{
8 db::{
9 queries::collection::{add_songs, read_songs, remove_songs},
10 schemas::{
11 collection::{Collection, CollectionChangeSet, CollectionId, TABLE_NAME},
12 playlist::Playlist,
13 song::{Song, SongId},
14 },
15 },
16 errors::{Error, StorageResult},
17};
18
19impl Collection {
20 #[instrument]
21 pub async fn create<C: Connection>(
22 db: &Surreal<C>,
23 collection: Self,
24 ) -> StorageResult<Option<Self>> {
25 Ok(db.create(collection.id.clone()).content(collection).await?)
26 }
27
28 #[instrument]
29 pub async fn read_all<C: Connection>(db: &Surreal<C>) -> StorageResult<Vec<Self>> {
30 Ok(db.select(TABLE_NAME).await?)
31 }
32
33 #[instrument]
34 pub async fn read<C: Connection>(
35 db: &Surreal<C>,
36 id: CollectionId,
37 ) -> StorageResult<Option<Self>> {
38 Ok(db.select(id).await?)
39 }
40
41 #[instrument]
42 pub async fn update<C: Connection>(
43 db: &Surreal<C>,
44 id: CollectionId,
45 changes: CollectionChangeSet,
46 ) -> StorageResult<Option<Self>> {
47 Ok(db.update(id).merge(changes).await?)
48 }
49
50 #[instrument]
51 pub async fn delete<C: Connection>(
52 db: &Surreal<C>,
53 id: CollectionId,
54 ) -> StorageResult<Option<Self>> {
55 let songs = Self::read_songs(db, id.clone())
57 .await?
58 .into_iter()
59 .map(|song| song.id)
60 .collect::<Vec<_>>();
61 Self::remove_songs(db, id.clone(), songs).await?;
62
63 Ok(db.delete(id).await?)
64 }
65
66 #[instrument]
67 pub async fn add_songs<C: Connection>(
68 db: &Surreal<C>,
69 id: CollectionId,
70 song_ids: Vec<SongId>,
71 ) -> StorageResult<()> {
72 db.query(add_songs())
73 .bind(("id", id.clone()))
74 .bind(("songs", song_ids))
75 .await?;
76 Self::repair(db, id).await?;
77 Ok(())
78 }
79
80 #[instrument]
81 pub async fn read_songs<C: Connection>(
82 db: &Surreal<C>,
83 id: CollectionId,
84 ) -> StorageResult<Vec<Song>> {
85 Ok(db.query(read_songs()).bind(("id", id)).await?.take(0)?)
86 }
87
88 #[instrument]
89 pub async fn remove_songs<C: Connection>(
95 db: &Surreal<C>,
96 id: CollectionId,
97 song_ids: Vec<SongId>,
98 ) -> StorageResult<bool> {
99 db.query(remove_songs())
100 .bind(("id", id.clone()))
101 .bind(("songs", song_ids))
102 .await?;
103 Self::repair(db, id).await
104 }
105
106 #[instrument]
116 pub async fn repair<C: Connection>(db: &Surreal<C>, id: CollectionId) -> StorageResult<bool> {
117 let songs = Self::read_songs(db, id.clone()).await?;
118
119 Self::update(
120 db,
121 id,
122 CollectionChangeSet {
123 song_count: Some(songs.len()),
124 runtime: Some(songs.iter().map(|song| song.runtime).sum::<Duration>()),
125 ..Default::default()
126 },
127 )
128 .await?;
129
130 Ok(songs.is_empty())
131 }
132
133 #[instrument]
135 pub async fn freeze<C: Connection>(
136 db: &Surreal<C>,
137 id: CollectionId,
138 name: String,
139 ) -> StorageResult<Playlist> {
140 let playlist = Playlist::create(
142 db,
143 Playlist {
144 id: Playlist::generate_id(),
145 name,
146 runtime: Duration::default(),
147 song_count: 0,
148 },
149 )
150 .await?
151 .ok_or(Error::NotFound)?;
152
153 let songs = Self::read_songs(db, id.clone()).await?;
155 let song_ids = songs.iter().map(|song| song.id.clone()).collect::<Vec<_>>();
156
157 Playlist::add_songs(db, playlist.id.clone(), song_ids).await?;
159
160 let playlist = Playlist::read(db, playlist.id.clone())
162 .await?
163 .ok_or(Error::NotFound)?;
164
165 Ok(playlist)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use std::time::Duration;
172
173 use super::*;
174 use crate::{
175 db::schemas::song::SongChangeSet,
176 test_utils::{arb_song_case, create_song_with_overrides, init_test_database},
177 };
178
179 use anyhow::{Result, anyhow};
180 use pretty_assertions::assert_eq;
181
182 fn create_collection() -> Collection {
183 Collection {
184 id: Collection::generate_id(),
185 name: "Test Collection".into(),
186 runtime: Duration::from_secs(0),
187 song_count: 0,
188 }
189 }
190
191 #[tokio::test]
192 async fn test_create() -> Result<()> {
193 let db = init_test_database().await?;
194 let collection = create_collection();
195 let result = Collection::create(&db, collection.clone()).await?;
196 assert_eq!(result, Some(collection));
197 Ok(())
198 }
199
200 #[tokio::test]
201 async fn test_read_all() -> Result<()> {
202 let db = init_test_database().await?;
203 let collection = create_collection();
204 Collection::create(&db, collection.clone()).await?;
205 let result = Collection::read_all(&db).await?;
206 assert!(!result.is_empty());
207 Ok(())
208 }
209
210 #[tokio::test]
211 async fn test_read() -> Result<()> {
212 let db = init_test_database().await?;
213 let collection = create_collection();
214 Collection::create(&db, collection.clone()).await?;
215 let result = Collection::read(&db, collection.id.clone()).await?;
216 assert_eq!(result, Some(collection));
217 Ok(())
218 }
219
220 #[tokio::test]
221 async fn test_update() -> Result<()> {
222 let db = init_test_database().await?;
223 let collection = create_collection();
224 Collection::create(&db, collection.clone()).await?;
225 let changes = CollectionChangeSet {
226 name: Some("Updated Name".into()),
227 ..Default::default()
228 };
229
230 let updated = Collection::update(&db, collection.id.clone(), changes).await?;
231 let read = Collection::read(&db, collection.id.clone())
232 .await?
233 .ok_or_else(|| anyhow!("Collection not found"))?;
234
235 assert_eq!(read.name, "Updated Name");
236 assert_eq!(Some(read), updated);
237 Ok(())
238 }
239
240 #[tokio::test]
241 async fn test_delete() -> Result<()> {
242 let db = init_test_database().await?;
243 let collection = create_collection();
244 Collection::create(&db, collection.clone()).await?;
245 let result = Collection::delete(&db, collection.id.clone()).await?;
246 assert_eq!(result, Some(collection.clone()));
247 let result = Collection::read(&db, collection.id).await?;
248 assert_eq!(result, None);
249 Ok(())
250 }
251
252 #[tokio::test]
253 async fn test_add_songs() -> Result<()> {
254 let db = init_test_database().await?;
255 let collection = create_collection();
256 Collection::create(&db, collection.clone()).await?;
257 let song =
258 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default()).await?;
259
260 Collection::add_songs(&db, collection.id.clone(), vec![song.id.clone()]).await?;
261
262 let result = Collection::read_songs(&db, collection.id.clone()).await?;
263 assert_eq!(result, vec![song.clone()]);
264
265 let read = Collection::read(&db, collection.id.clone())
266 .await?
267 .ok_or_else(|| anyhow!("Collection not found"))?;
268 assert_eq!(read.song_count, 1);
269 assert_eq!(read.runtime, song.runtime);
270
271 Ok(())
272 }
273
274 #[tokio::test]
275 async fn test_remove_songs() -> Result<()> {
276 let db = init_test_database().await?;
277 let collection = create_collection();
278 Collection::create(&db, collection.clone()).await?;
279 let song =
280 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default()).await?;
281
282 Collection::add_songs(&db, collection.id.clone(), vec![song.id.clone()]).await?;
283 assert!(Collection::remove_songs(&db, collection.id.clone(), vec![song.id.clone()]).await?);
284
285 let result = Collection::read_songs(&db, collection.id.clone()).await?;
286 assert_eq!(result, vec![]);
287
288 let read = Collection::read(&db, collection.id.clone())
289 .await?
290 .ok_or_else(|| anyhow!("Collection not found"))?;
291 assert_eq!(read.song_count, 0);
292 assert_eq!(read.runtime, Duration::from_secs(0));
293
294 Ok(())
295 }
296
297 #[tokio::test]
298 async fn test_freeze() -> Result<()> {
299 let db = init_test_database().await?;
300 let collection = create_collection();
301 Collection::create(&db, collection.clone()).await?;
302 let song =
303 create_song_with_overrides(&db, arb_song_case()(), SongChangeSet::default()).await?;
304
305 Collection::add_songs(&db, collection.id.clone(), vec![song.id.clone()]).await?;
306
307 let playlist =
308 Collection::freeze(&db, collection.id.clone(), "Frozen Playlist".into()).await?;
309
310 let songs = Playlist::read_songs(&db, playlist.id.clone()).await?;
311
312 assert_eq!(songs, vec![song.clone()]);
313 assert_eq!(playlist.song_count, 1);
314 assert_eq!(playlist.runtime, song.runtime);
315 assert_eq!(playlist.name, "Frozen Playlist");
316
317 Ok(())
318 }
319}