Skip to main content

headless_lms_models/library/entity_snapshots/
mod.rs

1//! Full snapshots of course content entities.
2//!
3//! Agent changesets stage these, `entity_history` stores them, and publishing and restoring write
4//! them back through the existing save paths. A snapshot holds every field its save path writes,
5//! so writing back an unchanged snapshot is a no-op.
6
7mod content_entity;
8mod loading;
9mod types;
10
11pub(crate) use content_entity::ContentEntity;
12pub use loading::{
13    course_settings_of, load_course_default_review_config, load_live, load_live_many, load_page,
14    load_pages,
15};
16pub use types::{
17    ChapterSnapshot, CourseAudienceSnapshot, CourseDefaultReviewConfigSnapshot,
18    CourseSettingsSnapshot, EntitySnapshot, ExerciseBundle, GlossaryTermSnapshot, ModuleSnapshot,
19    PageSnapshot, ReferenceSnapshot, exercise_bundles,
20};
21pub(crate) use types::{json_digest, match_snapshot, with_snapshot_type};
22
23use utoipa::ToSchema;
24
25use crate::prelude::*;
26
27/// A kind of course content entity that agent changesets stage and history records.
28#[derive(
29    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Type, ToSchema,
30)]
31#[sqlx(type_name = "content_entity_type", rename_all = "snake_case")]
32#[serde(rename_all = "snake_case")]
33pub enum ContentEntityType {
34    Page,
35    Chapter,
36    Module,
37    CourseSettings,
38    GlossaryTerm,
39    Reference,
40    CourseDefaultReviewConfig,
41}
42
43/// An entity kind and id, which together identify one content entity.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct EntityKey {
46    pub entity_type: ContentEntityType,
47    /// The entity's id; the course id for course settings.
48    pub entity_id: Uuid,
49}
50
51impl EntityKey {
52    /// The key of the entity.
53    pub fn new(entity_type: ContentEntityType, entity_id: Uuid) -> Self {
54        Self {
55            entity_type,
56            entity_id,
57        }
58    }
59
60    /// The types and ids of `keys` as two lists in the same order, for `UNNEST` parameters.
61    pub fn unzip(keys: &[EntityKey]) -> (Vec<ContentEntityType>, Vec<Uuid>) {
62        keys.iter()
63            .map(|key| (key.entity_type, key.entity_id))
64            .unzip()
65    }
66}
67
68/// A content entity and what people call it.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
70pub struct EntityRef {
71    pub entity_type: ContentEntityType,
72    /// The entity's id; the course id for course settings.
73    pub entity_id: Uuid,
74    /// A page title, chapter, module or course name, glossary term or citation key; empty for the
75    /// default module and the course default review config.
76    pub label: String,
77}
78
79impl EntityRef {
80    /// The entity, labelled from `preferred` if it exists and from `fallback` otherwise; with an
81    /// empty label if neither does.
82    pub fn of(
83        EntityKey {
84            entity_type,
85            entity_id,
86        }: EntityKey,
87        preferred: Option<&EntitySnapshot>,
88        fallback: Option<&EntitySnapshot>,
89    ) -> Self {
90        Self {
91            entity_type,
92            entity_id,
93            label: preferred
94                .or(fallback)
95                .map(|snapshot| snapshot.label().to_string())
96                .unwrap_or_default(),
97        }
98    }
99
100    /// The entity's key.
101    pub fn key(&self) -> EntityKey {
102        EntityKey::new(self.entity_type, self.entity_id)
103    }
104}