headless_lms_models/library/agent_changesets/course_state/
view.rs1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use super::{CourseParts, CourseState, ExerciseRecord, load_live_course};
4use crate::{
5 agent_changeset_entities::{self, AgentChangesetEntity},
6 agent_changesets::{self, AgentChangeset},
7 courses::Course,
8 library::entity_snapshots::{
9 self, ContentEntity, ContentEntityType, EntityKey, PageSnapshot, exercise_bundles,
10 },
11 prelude::*,
12};
13
14#[derive(Debug, Clone)]
16pub struct CourseView {
17 pub course: Course,
18 pub live: CourseState,
19 pub staged: CourseState,
20 pub changeset: Option<AgentChangeset>,
21 pub staged_entities: BTreeMap<EntityKey, AgentChangesetEntity>,
22}
23
24impl CourseView {
25 pub fn staged_row(&self, key: EntityKey) -> Option<&AgentChangesetEntity> {
27 self.staged_entities.get(&key)
28 }
29
30 pub fn exercise_page_id(&self, exercise_id: Uuid) -> Option<Uuid> {
32 self.staged.exercise_page_id(exercise_id).or_else(|| {
33 self.live
34 .exercise(exercise_id)
35 .map(|exercise| exercise.page_id)
36 })
37 }
38
39 pub fn staged_page_snapshot(&self, page_id: Uuid) -> Option<Option<&PageSnapshot>> {
42 let row = self.staged_row(EntityKey::new(ContentEntityType::Page, page_id))?;
43 Some(row.after_snapshot.as_ref().and_then(PageSnapshot::narrow))
44 }
45
46 pub async fn current_page_snapshots(
49 &self,
50 conn: &mut PgConnection,
51 page_ids: &[Uuid],
52 ) -> ModelResult<HashMap<Uuid, PageSnapshot>> {
53 let mut snapshots = HashMap::new();
54 let mut unstaged = Vec::new();
55 for page_id in page_ids {
56 match self.staged_page_snapshot(*page_id) {
57 Some(staged) => {
58 if let Some(page) = staged {
59 snapshots.insert(*page_id, page.clone());
60 }
61 }
62 None => unstaged.push(*page_id),
63 }
64 }
65 snapshots.extend(entity_snapshots::load_pages(conn, &unstaged).await?);
66 Ok(snapshots)
67 }
68
69 pub async fn load_exercises(&mut self, conn: &mut PgConnection) -> ModelResult<()> {
72 let page_ids: Vec<Uuid> = self.live.pages.iter().map(|page| page.id).collect();
73 self.load_exercise_records(conn, &page_ids).await
74 }
75
76 pub async fn load_exercises_on_pages(
80 &mut self,
81 conn: &mut PgConnection,
82 page_ids: &[Uuid],
83 ) -> ModelResult<()> {
84 if self.live.exercises.is_some() {
85 return Ok(());
86 }
87 let live_pages = self.live.exercise_page_ids();
88 let mut pages: HashSet<Uuid> = page_ids.iter().copied().collect();
89 let staged_exercises = page_ids
90 .iter()
91 .filter_map(|page_id| self.staged.page(*page_id))
92 .flat_map(|page| page.content.iter().filter_map(|block| block.exercise_id()));
93 pages.extend(staged_exercises.filter_map(|exercise_id| live_pages.get(&exercise_id)));
94 let pages: Vec<Uuid> = pages.into_iter().collect();
95 self.load_exercise_records(conn, &pages).await
96 }
97
98 async fn load_exercise_records(
101 &mut self,
102 conn: &mut PgConnection,
103 page_ids: &[Uuid],
104 ) -> ModelResult<()> {
105 if self.live.exercises.is_some() {
106 return Ok(());
107 }
108 let records = load_exercise_records(conn, page_ids).await?;
109 let staged_pages: HashMap<Uuid, Option<&PageSnapshot>> = self
110 .staged_entities
111 .values()
112 .filter(|row| row.entity_type == ContentEntityType::Page)
113 .map(|row| {
114 let page = row.after_snapshot.as_ref().and_then(PageSnapshot::narrow);
115 (row.entity_id, page)
116 })
117 .collect();
118 let mut staged = records.clone();
119 staged.retain(|_, exercise| !staged_pages.contains_key(&exercise.page_id));
120 for exercise in staged_pages
121 .values()
122 .flatten()
123 .flat_map(|page| ExerciseRecord::of_page(page))
124 {
125 staged.insert(exercise.id, exercise);
126 }
127 self.live.exercises = Some(records);
128 self.staged.exercises = Some(staged);
129 Ok(())
130 }
131}
132
133async fn load_exercise_records(
135 conn: &mut PgConnection,
136 page_ids: &[Uuid],
137) -> ModelResult<BTreeMap<Uuid, ExerciseRecord>> {
138 let mut records = BTreeMap::new();
139 for (page_id, exercises) in crate::pages::exercises_of_pages(conn, page_ids).await? {
140 for bundle in exercise_bundles(
141 &exercises.exercises,
142 &exercises.exercise_slides,
143 &exercises.exercise_tasks,
144 ) {
145 records.insert(
146 bundle.exercise.id,
147 ExerciseRecord::of_bundle(page_id, &bundle),
148 );
149 }
150 }
151 Ok(records)
152}
153
154pub async fn load_view(
156 conn: &mut PgConnection,
157 user_id: Uuid,
158 course: Course,
159 parts: CourseParts,
160) -> ModelResult<CourseView> {
161 let live = load_live_course(conn, &course, parts).await?;
162 let changeset = agent_changesets::get_open(conn, user_id, course.id).await?;
163 let staged_entities: BTreeMap<EntityKey, AgentChangesetEntity> = match &changeset {
164 Some(changeset) => agent_changeset_entities::get_by_changeset_id(conn, changeset.id)
165 .await?
166 .into_iter()
167 .map(|row| (row.key(), row))
168 .collect(),
169 None => BTreeMap::new(),
170 };
171 let staged = live.overlay(staged_entities.values(), parts);
172 Ok(CourseView {
173 course,
174 live,
175 staged,
176 changeset,
177 staged_entities,
178 })
179}