shared/domain/
playlist.rs

1//! Types for Playlists.
2
3use crate::domain::UpdateNonNullable;
4use chrono::{DateTime, Utc};
5use macros::make_path_parts;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use super::{
10    super::api::endpoints::PathPart,
11    additional_resource::AdditionalResource,
12    asset::{DraftOrLive, PrivacyLevel, UserOrMe},
13    category::CategoryId,
14    jig::JigId,
15    meta::{AffiliationId, AgeRangeId, ResourceTypeId},
16    module::LiteModule,
17    user::UserId,
18};
19
20wrap_uuid! {
21    /// Wrapper type around [`Uuid`], represents the ID of a Playlist.
22    pub struct PlaylistId
23}
24
25make_path_parts!(PlaylistCreatePath => "/v1/playlist");
26
27/// Request to create a new Playlist.
28///
29/// This creates the draft and live [Playlist Data](Playlist Data) copies with the requested info.
30#[derive(Serialize, Deserialize, Debug, Default)]
31#[serde(rename_all = "camelCase")]
32pub struct PlaylistCreateRequest {
33    /// The Playlist's name.
34    #[serde(default)]
35    pub display_name: String,
36
37    /// Description of the Playlist. Defaults to empty string.
38    #[serde(default)]
39    pub description: String,
40
41    /// This Playlist's age ranges.
42    #[serde(skip_serializing_if = "Vec::is_empty")]
43    #[serde(default)]
44    pub age_ranges: Vec<AgeRangeId>,
45
46    /// This Playlist's affiliations.
47    #[serde(skip_serializing_if = "Vec::is_empty")]
48    #[serde(default)]
49    pub affiliations: Vec<AffiliationId>,
50
51    /// The language the Playlist uses.
52    ///
53    /// NOTE: in the format `en`, `eng`, `en-US`, `eng-US` or `eng-USA`. To be replaced with a struct that enforces this.
54    #[serde(default)]
55    pub language: String,
56
57    /// The Playlist's categories.
58    #[serde(skip_serializing_if = "Vec::is_empty")]
59    #[serde(default)]
60    pub categories: Vec<CategoryId>,
61}
62
63/// The over-the-wire representation of a Playlist's data. This can either be the live copy or the draft copy.
64#[derive(Serialize, Deserialize, Clone, Debug)]
65#[serde(rename_all = "camelCase")]
66pub struct PlaylistData {
67    /// Whether the Playlist data is the live copy or the draft.
68    pub draft_or_live: DraftOrLive,
69
70    /// The Playlist's name.
71    pub display_name: String,
72
73    /// The language the Playlist uses.
74    ///
75    /// NOTE: in the format `en`, `eng`, `en-US`, `eng-US` or `eng-USA`. To be replaced with a struct that enforces this.
76    pub language: String,
77
78    /// Description of the Playlist.
79    pub description: String,
80
81    /// When the Playlist was last edited
82    pub last_edited: Option<DateTime<Utc>>,
83
84    /// The privacy level on the Playlist.
85    pub privacy_level: PrivacyLevel,
86
87    /// Other keywords used to searched for Playlists
88    pub other_keywords: String,
89
90    /// translated keywords used to searched for Playlists
91    pub translated_keywords: String,
92
93    /// translated descriptions
94    #[serde(default)]
95    pub translated_description: HashMap<String, String>,
96
97    /// This Playlist's cover.
98    pub cover: Option<LiteModule>,
99
100    /// This Playlist's age ranges.
101    pub age_ranges: Vec<AgeRangeId>,
102
103    /// This Playlist's affiliations.
104    pub affiliations: Vec<AffiliationId>,
105
106    /// The Playlist's categories.
107    pub categories: Vec<CategoryId>,
108
109    /// Additional resources of this Playlist.
110    pub additional_resources: Vec<AdditionalResource>,
111
112    /// List of Jig Ids within the Playlist
113    pub items: Vec<JigId>,
114}
115
116/// Admin rating for a course
117#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
118#[cfg_attr(feature = "backend", derive(sqlx::Type))]
119#[serde(rename_all = "camelCase")]
120#[repr(i16)]
121pub enum PlaylistRating {
122    #[allow(missing_docs)]
123    One = 1,
124    #[allow(missing_docs)]
125    Two = 2,
126    #[allow(missing_docs)]
127    Three = 3,
128}
129
130impl TryFrom<u8> for PlaylistRating {
131    type Error = ();
132
133    fn try_from(num: u8) -> Result<Self, Self::Error> {
134        match num {
135            1 => Ok(Self::One),
136            2 => Ok(Self::Two),
137            3 => Ok(Self::Three),
138            _ => Err(()),
139        }
140    }
141}
142
143/// These fields can be edited by admin and can be viewed by everyone
144#[derive(Serialize, Deserialize, Clone, Debug, Default)]
145#[serde(rename_all = "camelCase")]
146pub struct PlaylistAdminData {
147    /// Rating for jig, weighted for jig search
148    #[serde(default)]
149    pub rating: Option<PlaylistRating>,
150
151    /// if true does not appear in search
152    pub blocked: bool,
153
154    /// Indicates jig has been curated by admin
155    pub curated: bool,
156
157    /// Whether the resource is a premium resource
158    pub premium: bool,
159}
160
161/// The response returned when a request for `GET`ing a Playlist is successful.
162#[derive(Serialize, Deserialize, Debug, Clone)]
163#[serde(rename_all = "camelCase")]
164pub struct PlaylistResponse {
165    /// The ID of the Playlist.
166    pub id: PlaylistId,
167
168    /// When (if at all) the Playlist has published a draft to live.
169    pub published_at: Option<DateTime<Utc>>,
170
171    /// The ID of the Playlist's original creator ([`None`] if unknown).
172    pub creator_id: Option<UserId>,
173
174    /// The current author
175    pub author_id: Option<UserId>,
176
177    /// The author's name, as "{given_name} {family_name}".
178    pub author_name: Option<String>,
179
180    /// Number of likes on Playlist
181    pub likes: i64,
182
183    /// Number of plays Playlist
184    pub plays: i64,
185
186    /// Live is current to Draft
187    pub live_up_to_date: bool,
188
189    /// Liked by current user.
190    pub is_liked: bool,
191
192    /// The data of the requested Playlist.
193    pub playlist_data: PlaylistData,
194
195    /// Admin data for a course
196    pub admin_data: PlaylistAdminData,
197}
198
199make_path_parts!(PlaylistGetLivePath => "/v1/playlist/{}/live" => PlaylistId);
200
201make_path_parts!(PlaylistGetDraftPath => "/v1/playlist/{}/draft" => PlaylistId);
202
203make_path_parts!(PlaylistUpdateDraftDataPath => "/v1/playlist/{}" => PlaylistId);
204
205/// Request for updating a Playlist's draft data.
206#[derive(Serialize, Deserialize, Debug, Default)]
207#[serde(rename_all = "camelCase")]
208pub struct PlaylistUpdateDraftDataRequest {
209    /// The Playlist's name.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    #[serde(default)]
212    pub display_name: Option<String>,
213
214    /// The current author
215    #[serde(skip_serializing_if = "Option::is_none")]
216    #[serde(default)]
217    pub author_id: Option<UserId>,
218
219    /// Description of the Playlist.
220    #[serde(skip_serializing_if = "Option::is_none")]
221    #[serde(default)]
222    pub description: Option<String>,
223
224    /// The language the Playlist uses.
225    ///
226    /// NOTE: in the format `en`, `eng`, `en-US`, `eng-US` or `eng-USA`. To be replaced with a struct that enforces this.
227    #[serde(skip_serializing_if = "Option::is_none")]
228    #[serde(default)]
229    pub language: Option<String>,
230
231    /// Privacy level for the Playlist.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    #[serde(default)]
234    pub privacy_level: Option<PrivacyLevel>,
235
236    /// Additional keywords for searches
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[serde(default)]
239    pub other_keywords: Option<String>,
240
241    /// The Playlist's categories.
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[serde(default)]
244    pub categories: Option<Vec<CategoryId>>,
245
246    /// The Playlist's age ranges.
247    #[serde(skip_serializing_if = "Option::is_none")]
248    #[serde(default)]
249    pub age_ranges: Option<Vec<AgeRangeId>>,
250
251    /// The Playlist's affiliations.
252    #[serde(skip_serializing_if = "Option::is_none")]
253    #[serde(default)]
254    pub affiliations: Option<Vec<AffiliationId>>,
255
256    /// The Playlist's JIGs.
257    #[serde(skip_serializing_if = "Option::is_none")]
258    #[serde(default)]
259    pub items: Option<Vec<JigId>>,
260}
261
262make_path_parts!(PlaylistPublishPath => "/v1/playlist/{}/draft/publish" => PlaylistId);
263
264make_path_parts!(PlaylistBrowsePath => "/v1/playlist/browse");
265
266/// Query for [`Browse`](crate::api::endpoints::playlist::Browse).
267#[derive(Serialize, Deserialize, Clone, Debug, Default)]
268#[serde(rename_all = "camelCase")]
269pub struct PlaylistBrowseQuery {
270    /// Optionally filter by `is_published`
271    #[serde(default)]
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub is_published: Option<bool>,
274
275    /// Optionally filter by author id.
276    #[serde(default)]
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub author_id: Option<UserOrMe>,
279
280    /// The page number of the Playlists to get.
281    #[serde(default)]
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub page: Option<u32>,
284
285    /// Optionally browse by draft or live.
286    #[serde(default)]
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub draft_or_live: Option<DraftOrLive>,
289
290    /// Optionally filter Playlist by their privacy level
291    #[serde(default)]
292    #[serde(deserialize_with = "super::from_csv")]
293    #[serde(skip_serializing_if = "Vec::is_empty")]
294    pub privacy_level: Vec<PrivacyLevel>,
295
296    /// The hits per page to be returned
297    #[serde(default)]
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub page_limit: Option<u32>,
300
301    /// Optionally filter by `additional resources`
302    #[serde(default)]
303    #[serde(serialize_with = "super::csv_encode_uuids")]
304    #[serde(deserialize_with = "super::from_csv")]
305    #[serde(skip_serializing_if = "Vec::is_empty")]
306    pub resource_types: Vec<ResourceTypeId>,
307}
308
309/// Response for [`Browse`](crate::api::endpoints::playlist::Browse).
310#[derive(Serialize, Deserialize, Clone, Debug)]
311#[serde(rename_all = "camelCase")]
312pub struct PlaylistBrowseResponse {
313    /// the Playlists returned.
314    pub playlists: Vec<PlaylistResponse>,
315
316    /// The number of pages found.
317    pub pages: u32,
318
319    /// The total number of Playlists found
320    pub total_playlist_count: u64,
321}
322
323make_path_parts!(PlaylistSearchPath => "/v1/playlist");
324
325/// Search for Playlists via the given query string.
326#[derive(Serialize, Deserialize, Clone, Debug, Default)]
327#[serde(rename_all = "camelCase")]
328pub struct PlaylistSearchQuery {
329    /// The query string.
330    #[serde(default)]
331    #[serde(skip_serializing_if = "String::is_empty")]
332    pub q: String,
333
334    /// The page number of the Playlists to get.
335    #[serde(default)]
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub page: Option<u32>,
338
339    /// Optionally filter by `language`
340    #[serde(default)]
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub language: Option<String>,
343
344    /// Optionally filter by `is_published`. This means that the Playlist's `publish_at < now()`.
345    #[serde(default)]
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub is_published: Option<bool>,
348
349    /// Optionally filter by author's id
350    #[serde(default)]
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub author_id: Option<UserOrMe>,
353
354    /// Optionally filter by the author's name
355    #[serde(default)]
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub author_name: Option<String>,
358
359    /// Optionally search for Playlists using keywords
360    #[serde(default)]
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub other_keywords: Option<String>,
363
364    /// Optionally search for Playlists using translated keyword
365    #[serde(default)]
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub translated_keywords: Option<String>,
368
369    /// Optionally search for Playlists by privacy level
370    #[serde(default)]
371    #[serde(deserialize_with = "super::from_csv")]
372    #[serde(skip_serializing_if = "Vec::is_empty")]
373    pub privacy_level: Vec<PrivacyLevel>,
374
375    /// The hits per page to be returned
376    #[serde(default)]
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub page_limit: Option<u32>,
379
380    /// Optionally filter by `age_ranges`
381    ///
382    /// Note: Currently does nothing
383    #[serde(default)]
384    #[serde(serialize_with = "super::csv_encode_uuids")]
385    #[serde(deserialize_with = "super::from_csv")]
386    #[serde(skip_serializing_if = "Vec::is_empty")]
387    pub age_ranges: Vec<AgeRangeId>,
388
389    /// Optionally filter by `affiliations`
390    ///
391    /// Note: Currently does nothing
392    #[serde(default)]
393    #[serde(serialize_with = "super::csv_encode_uuids")]
394    #[serde(deserialize_with = "super::from_csv")]
395    #[serde(skip_serializing_if = "Vec::is_empty")]
396    pub affiliations: Vec<AffiliationId>,
397
398    /// Optionally filter by `additional resources`
399    #[serde(default)]
400    #[serde(serialize_with = "super::csv_encode_uuids")]
401    #[serde(deserialize_with = "super::from_csv")]
402    #[serde(skip_serializing_if = "Vec::is_empty")]
403    pub resource_types: Vec<ResourceTypeId>,
404
405    /// Optionally filter by `categories`
406    #[serde(default)]
407    #[serde(serialize_with = "super::csv_encode_uuids")]
408    #[serde(deserialize_with = "super::from_csv")]
409    #[serde(skip_serializing_if = "Vec::is_empty")]
410    pub categories: Vec<CategoryId>,
411
412    /// Optionally filter by `items`
413    #[serde(default)]
414    #[serde(serialize_with = "super::csv_encode_uuids")]
415    #[serde(deserialize_with = "super::from_csv")]
416    #[serde(skip_serializing_if = "Vec::is_empty")]
417    pub items: Vec<JigId>,
418
419    /// Optionally filter playlists based off of existence of rating
420    #[serde(default)]
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub is_rated: Option<bool>,
423}
424
425/// Response for successful search.
426#[derive(Serialize, Deserialize, Clone, Debug)]
427#[serde(rename_all = "camelCase")]
428pub struct PlaylistSearchResponse {
429    /// the Playlists returned.
430    pub playlists: Vec<PlaylistResponse>,
431
432    /// The number of pages found.
433    pub pages: u32,
434
435    /// The total number of Playlists found
436    pub total_playlist_count: u64,
437}
438
439/// Response for whether a user has liked a Playlist.
440#[derive(Serialize, Deserialize, Clone, Debug)]
441pub struct PlaylistLikedResponse {
442    /// Whether the authenticated user has liked the current Playlist
443    pub is_liked: bool,
444}
445
446/// These fields can be edited by admin and can be viewed by everyone
447#[derive(Serialize, Deserialize, Clone, Debug, Default)]
448#[serde(rename_all = "camelCase")]
449pub struct PlaylistUpdateAdminDataRequest {
450    /// Rating for jig, weighted for jig search
451    #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
452    pub rating: UpdateNonNullable<PlaylistRating>,
453
454    /// if true does not appear in search
455    #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
456    pub blocked: UpdateNonNullable<bool>,
457
458    /// Indicates jig has been curated by admin
459    #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
460    pub curated: UpdateNonNullable<bool>,
461
462    /// Indicates jig is premium content
463    #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
464    pub premium: UpdateNonNullable<bool>,
465}
466
467make_path_parts!(PlaylistDeletePath => "/v1/playlist/{}" => PlaylistId);
468
469make_path_parts!(PlaylistClonePath => "/v1/playlist/{}/clone" => PlaylistId);
470
471make_path_parts!(PlaylistLikePath => "/v1/playlist/{}/like" => PlaylistId);
472
473make_path_parts!(PlaylistUnlikePath => "/v1/playlist/{}/unlike" => PlaylistId);
474
475make_path_parts!(PlaylistLikedPath => "/v1/playlist/{}/like" => PlaylistId);
476
477make_path_parts!(ListLikedPath => "/v1/playlist/likes");
478
479/// Response for request for list of liked playlists.
480#[derive(Serialize, Deserialize, Clone, Debug, Default)]
481#[serde(rename_all = "camelCase")]
482pub struct ListLikedRequest {
483    /// The page number of the playlists to get.
484    #[serde(default)]
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub page: Option<u32>,
487
488    /// The hits per page to be returned
489    #[serde(default)]
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub page_limit: Option<u32>,
492}
493/// Response for request for list of liked playlists.
494#[derive(Serialize, Deserialize, Clone, Debug)]
495#[serde(rename_all = "camelCase")]
496pub struct ListLikedResponse {
497    /// the playlists returned.
498    pub playlists: Vec<PlaylistResponse>,
499
500    /// The total number of playlists liked
501    pub total_playlist_count: u64,
502}
503
504make_path_parts!(PlaylistViewPath => "/v1/playlist/{}/view" => PlaylistId);
505
506make_path_parts!(PlaylistAdminDataUpdatePath => "/v1/playlist/{}/admin" => PlaylistId);
507
508/// A playlists export representation.
509#[derive(Debug, Serialize, Deserialize, Clone)]
510pub struct AdminPlaylistExport {
511    /// playlist ID
512    pub id: PlaylistId,
513    /// Description of the playlist.
514    pub description: String,
515    /// The playlist's name.
516    pub display_name: String,
517    /// Whether the resource is a premium resource
518    pub premium: bool,
519    /// if true does not appear in search
520    pub blocked: bool,
521    /// The current author
522    pub author_id: Option<UserId>,
523    /// The author's name, as "{given_name} {family_name}".
524    pub author_name: Option<String>,
525    /// Number of likes on playlist
526    pub likes: i64,
527    /// Number of plays playlist
528    pub plays: i64,
529    /// Rating for playlist, weighted for playlist search
530    pub rating: Option<PlaylistRating>,
531    /// The privacy level on the playlist.
532    pub privacy_level: PrivacyLevel,
533    /// When the playlist was first created.
534    pub created_at: DateTime<Utc>,
535    /// When (if at all) the playlist has published a draft to live.
536    pub published_at: Option<DateTime<Utc>>,
537    /// The language the playlist uses.
538    pub language: String,
539}