1use 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 pub struct PlaylistId
23}
24
25make_path_parts!(PlaylistCreatePath => "/v1/playlist");
26
27#[derive(Serialize, Deserialize, Debug, Default)]
31#[serde(rename_all = "camelCase")]
32pub struct PlaylistCreateRequest {
33 #[serde(default)]
35 pub display_name: String,
36
37 #[serde(default)]
39 pub description: String,
40
41 #[serde(skip_serializing_if = "Vec::is_empty")]
43 #[serde(default)]
44 pub age_ranges: Vec<AgeRangeId>,
45
46 #[serde(skip_serializing_if = "Vec::is_empty")]
48 #[serde(default)]
49 pub affiliations: Vec<AffiliationId>,
50
51 #[serde(default)]
55 pub language: String,
56
57 #[serde(skip_serializing_if = "Vec::is_empty")]
59 #[serde(default)]
60 pub categories: Vec<CategoryId>,
61}
62
63#[derive(Serialize, Deserialize, Clone, Debug)]
65#[serde(rename_all = "camelCase")]
66pub struct PlaylistData {
67 pub draft_or_live: DraftOrLive,
69
70 pub display_name: String,
72
73 pub language: String,
77
78 pub description: String,
80
81 pub last_edited: Option<DateTime<Utc>>,
83
84 pub privacy_level: PrivacyLevel,
86
87 pub other_keywords: String,
89
90 pub translated_keywords: String,
92
93 #[serde(default)]
95 pub translated_description: HashMap<String, String>,
96
97 pub cover: Option<LiteModule>,
99
100 pub age_ranges: Vec<AgeRangeId>,
102
103 pub affiliations: Vec<AffiliationId>,
105
106 pub categories: Vec<CategoryId>,
108
109 pub additional_resources: Vec<AdditionalResource>,
111
112 pub items: Vec<JigId>,
114}
115
116#[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#[derive(Serialize, Deserialize, Clone, Debug, Default)]
145#[serde(rename_all = "camelCase")]
146pub struct PlaylistAdminData {
147 #[serde(default)]
149 pub rating: Option<PlaylistRating>,
150
151 pub blocked: bool,
153
154 pub curated: bool,
156
157 pub premium: bool,
159}
160
161#[derive(Serialize, Deserialize, Debug, Clone)]
163#[serde(rename_all = "camelCase")]
164pub struct PlaylistResponse {
165 pub id: PlaylistId,
167
168 pub published_at: Option<DateTime<Utc>>,
170
171 pub creator_id: Option<UserId>,
173
174 pub author_id: Option<UserId>,
176
177 pub author_name: Option<String>,
179
180 pub likes: i64,
182
183 pub plays: i64,
185
186 pub live_up_to_date: bool,
188
189 pub is_liked: bool,
191
192 pub playlist_data: PlaylistData,
194
195 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#[derive(Serialize, Deserialize, Debug, Default)]
207#[serde(rename_all = "camelCase")]
208pub struct PlaylistUpdateDraftDataRequest {
209 #[serde(skip_serializing_if = "Option::is_none")]
211 #[serde(default)]
212 pub display_name: Option<String>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
216 #[serde(default)]
217 pub author_id: Option<UserId>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 #[serde(default)]
222 pub description: Option<String>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
228 #[serde(default)]
229 pub language: Option<String>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 #[serde(default)]
234 pub privacy_level: Option<PrivacyLevel>,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
238 #[serde(default)]
239 pub other_keywords: Option<String>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
243 #[serde(default)]
244 pub categories: Option<Vec<CategoryId>>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 #[serde(default)]
249 pub age_ranges: Option<Vec<AgeRangeId>>,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
253 #[serde(default)]
254 pub affiliations: Option<Vec<AffiliationId>>,
255
256 #[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#[derive(Serialize, Deserialize, Clone, Debug, Default)]
268#[serde(rename_all = "camelCase")]
269pub struct PlaylistBrowseQuery {
270 #[serde(default)]
272 #[serde(skip_serializing_if = "Option::is_none")]
273 pub is_published: Option<bool>,
274
275 #[serde(default)]
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub author_id: Option<UserOrMe>,
279
280 #[serde(default)]
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub page: Option<u32>,
284
285 #[serde(default)]
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub draft_or_live: Option<DraftOrLive>,
289
290 #[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 #[serde(default)]
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub page_limit: Option<u32>,
300
301 #[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#[derive(Serialize, Deserialize, Clone, Debug)]
311#[serde(rename_all = "camelCase")]
312pub struct PlaylistBrowseResponse {
313 pub playlists: Vec<PlaylistResponse>,
315
316 pub pages: u32,
318
319 pub total_playlist_count: u64,
321}
322
323make_path_parts!(PlaylistSearchPath => "/v1/playlist");
324
325#[derive(Serialize, Deserialize, Clone, Debug, Default)]
327#[serde(rename_all = "camelCase")]
328pub struct PlaylistSearchQuery {
329 #[serde(default)]
331 #[serde(skip_serializing_if = "String::is_empty")]
332 pub q: String,
333
334 #[serde(default)]
336 #[serde(skip_serializing_if = "Option::is_none")]
337 pub page: Option<u32>,
338
339 #[serde(default)]
341 #[serde(skip_serializing_if = "Option::is_none")]
342 pub language: Option<String>,
343
344 #[serde(default)]
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub is_published: Option<bool>,
348
349 #[serde(default)]
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub author_id: Option<UserOrMe>,
353
354 #[serde(default)]
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub author_name: Option<String>,
358
359 #[serde(default)]
361 #[serde(skip_serializing_if = "Option::is_none")]
362 pub other_keywords: Option<String>,
363
364 #[serde(default)]
366 #[serde(skip_serializing_if = "Option::is_none")]
367 pub translated_keywords: Option<String>,
368
369 #[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 #[serde(default)]
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub page_limit: Option<u32>,
379
380 #[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 #[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 #[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 #[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 #[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 #[serde(default)]
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub is_rated: Option<bool>,
423}
424
425#[derive(Serialize, Deserialize, Clone, Debug)]
427#[serde(rename_all = "camelCase")]
428pub struct PlaylistSearchResponse {
429 pub playlists: Vec<PlaylistResponse>,
431
432 pub pages: u32,
434
435 pub total_playlist_count: u64,
437}
438
439#[derive(Serialize, Deserialize, Clone, Debug)]
441pub struct PlaylistLikedResponse {
442 pub is_liked: bool,
444}
445
446#[derive(Serialize, Deserialize, Clone, Debug, Default)]
448#[serde(rename_all = "camelCase")]
449pub struct PlaylistUpdateAdminDataRequest {
450 #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
452 pub rating: UpdateNonNullable<PlaylistRating>,
453
454 #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
456 pub blocked: UpdateNonNullable<bool>,
457
458 #[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
460 pub curated: UpdateNonNullable<bool>,
461
462 #[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#[derive(Serialize, Deserialize, Clone, Debug, Default)]
481#[serde(rename_all = "camelCase")]
482pub struct ListLikedRequest {
483 #[serde(default)]
485 #[serde(skip_serializing_if = "Option::is_none")]
486 pub page: Option<u32>,
487
488 #[serde(default)]
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub page_limit: Option<u32>,
492}
493#[derive(Serialize, Deserialize, Clone, Debug)]
495#[serde(rename_all = "camelCase")]
496pub struct ListLikedResponse {
497 pub playlists: Vec<PlaylistResponse>,
499
500 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#[derive(Debug, Serialize, Deserialize, Clone)]
510pub struct AdminPlaylistExport {
511 pub id: PlaylistId,
513 pub description: String,
515 pub display_name: String,
517 pub premium: bool,
519 pub blocked: bool,
521 pub author_id: Option<UserId>,
523 pub author_name: Option<String>,
525 pub likes: i64,
527 pub plays: i64,
529 pub rating: Option<PlaylistRating>,
531 pub privacy_level: PrivacyLevel,
533 pub created_at: DateTime<Utc>,
535 pub published_at: Option<DateTime<Utc>>,
537 pub language: String,
539}