use super::asset::{AssetId, AssetType};
use crate::api::endpoints::PathPart;
use chrono::{DateTime, Utc};
use macros::make_path_parts;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub mod body;
pub use body::Body as ModuleBody;
wrap_uuid! {
#[serde(rename_all = "camelCase")]
pub struct ModuleId
}
wrap_uuid! {
#[serde(rename_all = "camelCase")]
pub struct StableModuleId
}
#[repr(i16)]
#[cfg_attr(feature = "backend", derive(sqlx::Type))]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub enum ModuleKind {
Cover = 0,
Flashcards = 1,
Matching = 2,
Memory = 3,
Poster = 4,
TappingBoard = 5,
Tracing = 6,
Video = 7,
CardQuiz = 9,
DragDrop = 10,
Legacy = 11,
ResourceCover = 12,
FindAnswer = 13,
Embed = 14,
}
impl ModuleKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Cover => "cover",
Self::ResourceCover => "resource-cover",
Self::Flashcards => "flashcards",
Self::Matching => "matching",
Self::Memory => "memory",
Self::Poster => "poster",
Self::TappingBoard => "tapping-board",
Self::DragDrop => "drag-drop",
Self::Tracing => "tracing",
Self::Video => "video",
Self::Embed => "embed",
Self::CardQuiz => "card-quiz",
Self::Legacy => "legacy",
Self::FindAnswer => "find-answer",
}
}
pub fn display_name(self) -> &'static str {
match self {
Self::Cover => "Cover",
Self::ResourceCover => "Cover",
Self::Flashcards => "Flashcards",
Self::Matching => "Matching",
Self::Memory => "Memory Game",
Self::Poster => "Talking Poster",
Self::TappingBoard => "Listen & Learn",
Self::Tracing => "Tracing",
Self::Video => "Video Player",
Self::Embed => "Embed It",
Self::CardQuiz => "Multiple Choice",
Self::DragDrop => "Drag & Drop",
Self::FindAnswer => "Answer This",
Self::Legacy => "Legacy",
}
}
pub fn autogenerated(&self) -> bool {
match self {
Self::Flashcards | Self::Matching | Self::Memory | Self::CardQuiz => true,
_ => false,
}
}
pub fn has_scoring(&self) -> bool {
match self {
Self::Matching | Self::CardQuiz | Self::DragDrop | Self::FindAnswer => true,
_ => false,
}
}
}
impl FromStr for ModuleKind {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let res = match s {
"cover" => Self::Cover,
"resource-cover" => Self::ResourceCover,
"flashcards" => Self::Flashcards,
"matching" => Self::Matching,
"memory" => Self::Memory,
"poster" => Self::Poster,
"tapping-board" => Self::TappingBoard,
"drag-drop" => Self::DragDrop,
"tracing" => Self::Tracing,
"video" => Self::Video,
"embed" => Self::Embed,
"card-quiz" => Self::CardQuiz,
"legacy" => Self::Legacy,
"find-answer" => Self::FindAnswer,
_ => anyhow::bail!("Invalid ModuleKind: {}", s),
};
Ok(res)
}
}
impl ModuleBody {
pub fn map_module_contents(body: &Self) -> anyhow::Result<(ModuleKind, serde_json::Value)> {
let kind = body.kind();
let body = match body {
Self::CardQuiz(body) => serde_json::to_value(body)?,
Self::Cover(body) => serde_json::to_value(body)?,
Self::ResourceCover(body) => serde_json::to_value(body)?,
Self::DragDrop(body) => serde_json::to_value(body)?,
Self::Flashcards(body) => serde_json::to_value(body)?,
Self::Matching(body) => serde_json::to_value(body)?,
Self::MemoryGame(body) => serde_json::to_value(body)?,
Self::Poster(body) => serde_json::to_value(body)?,
Self::TappingBoard(body) => serde_json::to_value(body)?,
Self::Video(body) => serde_json::to_value(body)?,
Self::Embed(body) => serde_json::to_value(body)?,
Self::FindAnswer(body) => serde_json::to_value(body)?,
Self::Legacy(body) => serde_json::to_value(body)?,
};
Ok((kind, body))
}
pub fn transform_response_kind(
contents: serde_json::Value,
kind: ModuleKind,
) -> anyhow::Result<Self> {
match kind {
ModuleKind::CardQuiz => Ok(Self::CardQuiz(serde_json::from_value(contents)?)),
ModuleKind::Cover => Ok(Self::Cover(serde_json::from_value(contents)?)),
ModuleKind::ResourceCover => Ok(Self::ResourceCover(serde_json::from_value(contents)?)),
ModuleKind::DragDrop => Ok(Self::DragDrop(serde_json::from_value(contents)?)),
ModuleKind::Flashcards => Ok(Self::Flashcards(serde_json::from_value(contents)?)),
ModuleKind::Matching => Ok(Self::Matching(serde_json::from_value(contents)?)),
ModuleKind::Memory => Ok(Self::MemoryGame(serde_json::from_value(contents)?)),
ModuleKind::Poster => Ok(Self::Poster(serde_json::from_value(contents)?)),
ModuleKind::TappingBoard => Ok(Self::TappingBoard(serde_json::from_value(contents)?)),
ModuleKind::Video => Ok(Self::Video(serde_json::from_value(contents)?)),
ModuleKind::Embed => Ok(Self::Embed(serde_json::from_value(contents)?)),
ModuleKind::FindAnswer => Ok(Self::FindAnswer(serde_json::from_value(contents)?)),
ModuleKind::Legacy => Ok(Self::Legacy(serde_json::from_value(contents)?)),
_ => anyhow::bail!("Unimplemented response kind"),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct LiteModule {
pub id: ModuleId,
pub stable_id: StableModuleId,
pub kind: ModuleKind,
#[serde(default)]
pub is_complete: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Module {
pub id: ModuleId,
pub stable_id: StableModuleId,
pub body: ModuleBody,
pub is_complete: bool,
pub is_updated: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
make_path_parts!(ModuleCreatePath => "/v1/module/draft");
#[derive(Serialize, Deserialize, Debug)]
pub struct ModuleCreateRequest {
#[serde(flatten)]
pub parent_id: AssetId,
pub body: ModuleBody,
}
make_path_parts!(ModuleGetLivePath => "/v1/{}/module/live/{}" => AssetType, ModuleId);
make_path_parts!(ModuleGetDraftPath => "/v1/{}/module/draft/{}" => AssetType, ModuleId);
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModuleResponse {
pub module: Module,
}
make_path_parts!(ModuleUploadPath => "/v1/module/draft/{}" => ModuleId);
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ModuleUpdateRequest {
#[serde(flatten)]
pub parent_id: AssetId,
#[serde(default)]
pub body: Option<ModuleBody>,
#[serde(default)]
pub index: Option<u16>,
#[serde(default)]
pub is_complete: Option<bool>,
}
make_path_parts!(ModuleDeletePath => "/v1/module/draft/{}" => ModuleId);
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ModuleDeleteRequest {
#[serde(flatten)]
pub parent_id: AssetId,
}