headless_lms_models/library/entity_snapshots/
loading.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use super::{
5 ChapterSnapshot, ContentEntity, ContentEntityType, CourseAudienceSnapshot,
6 CourseDefaultReviewConfigSnapshot, CourseSettingsSnapshot, EntityKey, EntitySnapshot,
7 GlossaryTermSnapshot, ModuleSnapshot, PageSnapshot, ReferenceSnapshot, with_snapshot_type,
8};
9use crate::{course_modules, pages::PageVisibility, prelude::*};
10
11pub async fn load_course_default_review_config(
13 conn: &mut PgConnection,
14 course_id: Uuid,
15) -> ModelResult<Option<CourseDefaultReviewConfigSnapshot>> {
16 let config =
17 crate::peer_or_self_review_configs::get_default_for_course_by_course_id(conn, course_id)
18 .await
19 .optional()?;
20 Ok(config.map(CourseDefaultReviewConfigSnapshot::from))
21}
22
23pub async fn load_live_many(
26 conn: &mut PgConnection,
27 keys: &[EntityKey],
28) -> ModelResult<HashMap<EntityKey, Option<EntitySnapshot>>> {
29 let mut snapshots: HashMap<EntityKey, Option<EntitySnapshot>> =
30 keys.iter().map(|key| (*key, None)).collect();
31 let mut ids_by_type: HashMap<ContentEntityType, Vec<Uuid>> = HashMap::new();
32 for EntityKey {
33 entity_type,
34 entity_id,
35 } in snapshots.keys()
36 {
37 ids_by_type
38 .entry(*entity_type)
39 .or_default()
40 .push(*entity_id);
41 }
42 for (entity_type, ids) in ids_by_type {
43 with_snapshot_type!(entity_type, T => {
44 for (id, snapshot) in T::load_live(conn, &ids).await? {
45 snapshots.insert(EntityKey::new(entity_type, id), Some(snapshot.into()));
46 }
47 });
48 }
49 Ok(snapshots)
50}
51
52pub async fn load_live(
54 conn: &mut PgConnection,
55 key: EntityKey,
56) -> ModelResult<Option<EntitySnapshot>> {
57 Ok(load_live_many(conn, &[key]).await?.remove(&key).flatten())
58}
59
60pub async fn load_page(
62 conn: &mut PgConnection,
63 page_id: Uuid,
64) -> ModelResult<Option<PageSnapshot>> {
65 Ok(load_pages(conn, &[page_id]).await?.remove(&page_id))
66}
67
68pub async fn load_pages(
70 conn: &mut PgConnection,
71 page_ids: &[Uuid],
72) -> ModelResult<HashMap<Uuid, PageSnapshot>> {
73 if page_ids.is_empty() {
74 return Ok(HashMap::new());
75 }
76 let pages: Vec<_> =
77 crate::pages::get_by_ids_and_visibility(conn, page_ids, PageVisibility::Any)
78 .await?
79 .into_iter()
80 .filter(|page| page.exam_id.is_none())
81 .filter_map(|page| Some((page.course_id?, page)))
82 .collect();
83 let mut exercises = crate::pages::exercises_of_pages(
84 conn,
85 &pages.iter().map(|(_, page)| page.id).collect::<Vec<_>>(),
86 )
87 .await?;
88 let mut snapshots = HashMap::with_capacity(pages.len());
89 for (course_id, page) in pages {
90 let page_exercises = exercises.remove(&page.id).unwrap_or_default();
91 let mut snapshot = PageSnapshot {
92 id: page.id,
93 course_id,
94 chapter_id: page.chapter_id,
95 url_path: page.url_path,
96 title: page.title,
97 hidden: page.hidden,
98 order_number: page.order_number,
99 content: Arc::new(serde_json::from_value(page.content)?),
100 exercises: page_exercises.exercises,
101 exercise_slides: page_exercises.exercise_slides,
102 exercise_tasks: page_exercises.exercise_tasks,
103 joins_page_language_group_id: None,
104 };
105 snapshot.sort();
106 snapshots.insert(snapshot.id, snapshot);
107 }
108 Ok(snapshots)
109}
110
111impl From<course_modules::CourseModule> for ModuleSnapshot {
112 fn from(module: course_modules::CourseModule) -> Self {
113 Self {
114 id: module.id,
115 course_id: module.course_id,
116 name: module.name,
117 order_number: module.order_number,
118 completion_policy: module.completion_policy,
119 certification_enabled: module.certification_enabled,
120 uh_course_code: module.uh_course_code,
121 ects_credits: module.ects_credits,
122 completion_registration_link_override: module.completion_registration_link_override,
123 completions_can_be_registered_to_sisu: module.completions_can_be_registered_to_sisu,
124 }
125 }
126}
127
128impl From<crate::chapters::DatabaseChapter> for ChapterSnapshot {
129 fn from(chapter: crate::chapters::DatabaseChapter) -> Self {
130 Self {
131 id: chapter.id,
132 course_id: chapter.course_id,
133 course_module_id: chapter.course_module_id,
134 name: chapter.name,
135 color: chapter.color,
136 chapter_number: chapter.chapter_number,
137 front_page_id: chapter.front_page_id,
138 opens_at: chapter.opens_at,
139 deadline: chapter.deadline,
140 chapter_image_path: chapter.chapter_image_path,
141 }
142 }
143}
144
145pub async fn course_settings_of(
147 conn: &mut PgConnection,
148 course: &crate::courses::Course,
149) -> ModelResult<CourseSettingsSnapshot> {
150 let mut audiences: Vec<CourseAudienceSnapshot> =
151 crate::course_audiences::get_edit_course_audiences_by_course_id(conn, course.id)
152 .await?
153 .into_iter()
154 .map(|audience| CourseAudienceSnapshot {
155 id: audience.id,
156 audience: audience.audience,
157 })
158 .collect();
159 audiences.sort_by_key(|audience| audience.id);
160 Ok(CourseSettingsSnapshot {
161 course_id: course.id,
162 name: course.name.clone(),
163 description: course.description.clone(),
164 is_draft: course.is_draft,
165 is_test_mode: course.is_test_mode,
166 is_unlisted: course.is_unlisted,
167 can_add_chatbot: course.can_add_chatbot,
168 is_joinable_by_code_only: course.is_joinable_by_code_only,
169 ask_marketing_consent: course.ask_marketing_consent,
170 flagged_answers_threshold: course.flagged_answers_threshold,
171 flagged_answers_skip_manual_review_and_allow_retry: course
172 .flagged_answers_skip_manual_review_and_allow_retry,
173 closed_at: course.closed_at,
174 closed_additional_message: course.closed_additional_message.clone(),
175 closed_course_successor_id: course.closed_course_successor_id,
176 chapter_locking_enabled: course.chapter_locking_enabled,
177 ai_policy: course.ai_policy,
178 course_material_ai_instructions: course.course_material_ai_instructions,
179 base_module_completion_requires_n_submodule_completions: course
180 .base_module_completion_requires_n_submodule_completions,
181 audiences,
182 })
183}
184
185impl From<crate::glossary::Term> for GlossaryTermSnapshot {
186 fn from(term: crate::glossary::Term) -> Self {
187 Self {
188 id: term.id,
189 course_id: term.course_id,
190 term: term.term,
191 definition: term.definition,
192 }
193 }
194}
195
196impl From<crate::material_references::MaterialReference> for ReferenceSnapshot {
197 fn from(reference: crate::material_references::MaterialReference) -> Self {
198 Self {
199 id: reference.id,
200 course_id: reference.course_id,
201 citation_key: reference.citation_key,
202 reference: reference.reference,
203 }
204 }
205}
206
207impl From<crate::peer_or_self_review_configs::PeerOrSelfReviewConfig>
208 for CourseDefaultReviewConfigSnapshot
209{
210 fn from(config: crate::peer_or_self_review_configs::PeerOrSelfReviewConfig) -> Self {
211 Self {
212 id: config.id,
213 course_id: config.course_id,
214 review_instructions: config.review_instructions,
215 }
216 }
217}