use crate::domain::UpdateNonNullable;
use chrono::{DateTime, Utc};
use macros::make_path_parts;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use strum_macros::Display;
use self::unit::{CourseUnit, CourseUnitId};
use super::{
super::api::endpoints::PathPart,
additional_resource::AdditionalResource,
asset::{DraftOrLive, PrivacyLevel, UserOrMe},
category::CategoryId,
meta::ResourceTypeId,
module::LiteModule,
user::UserId,
};
pub mod unit;
wrap_uuid! {
pub struct CourseId
}
make_path_parts!(CourseCreatePath => "/v1/course");
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseCreateRequest {
#[serde(default)]
pub display_name: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub language: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub categories: Vec<CategoryId>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CourseData {
pub draft_or_live: DraftOrLive,
pub display_name: String,
pub language: String,
pub description: String,
pub last_edited: Option<DateTime<Utc>>,
pub duration: Option<u32>,
pub privacy_level: PrivacyLevel,
pub other_keywords: String,
pub translated_keywords: String,
#[serde(default)]
pub translated_description: HashMap<String, String>,
pub cover: Option<LiteModule>,
pub categories: Vec<CategoryId>,
pub additional_resources: Vec<AdditionalResource>,
pub units: Vec<CourseUnit>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "backend", derive(sqlx::Type))]
#[serde(rename_all = "camelCase")]
#[repr(i16)]
pub enum CourseRating {
#[allow(missing_docs)]
One = 1,
#[allow(missing_docs)]
Two = 2,
#[allow(missing_docs)]
Three = 3,
}
impl TryFrom<u8> for CourseRating {
type Error = ();
fn try_from(num: u8) -> Result<Self, Self::Error> {
match num {
1 => Ok(Self::One),
2 => Ok(Self::Two),
3 => Ok(Self::Three),
_ => Err(()),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseAdminData {
#[serde(default)]
pub rating: Option<CourseRating>,
pub blocked: bool,
pub curated: bool,
pub premium: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CourseResponse {
pub id: CourseId,
pub published_at: Option<DateTime<Utc>>,
pub creator_id: Option<UserId>,
pub author_id: Option<UserId>,
pub author_name: Option<String>,
pub likes: i64,
pub plays: i64,
pub live_up_to_date: bool,
pub course_data: CourseData,
pub admin_data: CourseAdminData,
}
make_path_parts!(CourseGetLivePath => "/v1/course/{}/live" => CourseId);
make_path_parts!(CourseGetDraftPath => "/v1/course/{}/draft" => CourseId);
make_path_parts!(CourseUpdateDraftDataPath => "/v1/course/{}" => CourseId);
make_path_parts!(CourseClonePath => "/v1/course/{}/clone" => CourseId);
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseUpdateDraftDataRequest {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub author_id: Option<UserId>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub duration: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub privacy_level: Option<PrivacyLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub other_keywords: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub categories: Option<Vec<CategoryId>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub units: Option<Vec<CourseUnitId>>,
}
make_path_parts!(CoursePublishPath => "/v1/course/{}/draft/publish" => CourseId);
make_path_parts!(CourseBrowsePath => "/v1/course/browse");
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseBrowseQuery {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_published: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub author_id: Option<UserOrMe>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub draft_or_live: Option<DraftOrLive>,
#[serde(default)]
#[serde(deserialize_with = "super::from_csv")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub privacy_level: Vec<PrivacyLevel>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub blocked: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub page_limit: Option<u32>,
#[serde(default)]
#[serde(serialize_with = "super::csv_encode_uuids")]
#[serde(deserialize_with = "super::from_csv")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub resource_types: Vec<ResourceTypeId>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub order_by: Option<OrderBy>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CourseBrowseResponse {
pub courses: Vec<CourseResponse>,
pub pages: u32,
pub total_course_count: u64,
}
make_path_parts!(CourseSearchPath => "/v1/course");
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseSearchQuery {
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub q: String,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_published: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub author_id: Option<UserOrMe>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub author_name: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub other_keywords: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub translated_keywords: Option<String>,
#[serde(default)]
#[serde(deserialize_with = "super::from_csv")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub privacy_level: Vec<PrivacyLevel>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub blocked: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub page_limit: Option<u32>,
#[serde(default)]
#[serde(serialize_with = "super::csv_encode_uuids")]
#[serde(deserialize_with = "super::from_csv")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub resource_types: Vec<ResourceTypeId>,
#[serde(default)]
#[serde(serialize_with = "super::csv_encode_uuids")]
#[serde(deserialize_with = "super::from_csv")]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub categories: Vec<CategoryId>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CourseSearchResponse {
pub courses: Vec<CourseResponse>,
pub pages: u32,
pub total_course_count: u64,
}
make_path_parts!(CourseDeletePath => "/v1/course/{}" => CourseId);
make_path_parts!(CoursePlayPath => "/v1/course/{}/play" => CourseId);
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug, Display)]
#[cfg_attr(feature = "backend", derive(sqlx::Type))]
#[serde(rename_all = "camelCase")]
#[repr(i16)]
pub enum OrderBy {
#[strum(serialize = "PlayCount")]
PlayCount = 0,
}
make_path_parts!(CourseAdminDataUpdatePath => "/v1/course/{}/admin" => CourseId);
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct CourseUpdateAdminDataRequest {
#[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
pub rating: UpdateNonNullable<CourseRating>,
#[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
pub blocked: UpdateNonNullable<bool>,
#[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
pub curated: UpdateNonNullable<bool>,
#[serde(default, skip_serializing_if = "UpdateNonNullable::is_keep")]
pub premium: UpdateNonNullable<bool>,
}