headless_lms_models/library/
content_management.rs1use futures::future::BoxFuture;
2use headless_lms_utils::document_schema_processor::GutenbergBlock;
3use url::Url;
4
5use crate::{
6 SpecFetcher,
7 chapters::{self, DatabaseChapter, NewChapter},
8 course_instances::{CourseInstance, NewCourseInstance},
9 course_language_groups,
10 course_modules::{CourseModule, NewCourseModule},
11 courses::{self, Course, NewCourse},
12 exercise_service_info::ExerciseServiceInfoApi,
13 pages::{self, NewPage, Page},
14 peer_or_self_review_questions::CmsPeerOrSelfReviewQuestion,
15 prelude::*,
16};
17
18#[derive(Debug, Clone)]
19pub struct CreateNewCourseFixedIds {
20 pub course_id: Uuid,
21 pub default_course_instance_id: Uuid,
22}
23
24pub async fn create_new_course(
26 conn: &mut PgConnection,
27 pkey_policy: PKeyPolicy<CreateNewCourseFixedIds>,
28 new_course: NewCourse,
29 user: Uuid,
30 spec_fetcher: impl SpecFetcher,
31 fetch_service_info: impl Fn(Url) -> BoxFuture<'static, ModelResult<ExerciseServiceInfoApi>>,
32) -> ModelResult<(Course, Page, CourseInstance, CourseModule)> {
33 let mut tx = conn.begin().await?;
34
35 let course_language_group_id =
36 course_language_groups::insert(&mut tx, PKeyPolicy::Generate).await?;
37
38 let course_id = courses::insert(
39 &mut tx,
40 pkey_policy.map_ref(|x| x.course_id),
41 course_language_group_id,
42 &new_course,
43 )
44 .await?;
45 let course = courses::get_course(&mut tx, course_id).await?;
46
47 let course_front_page_content = serde_json::to_value(vec![
49 GutenbergBlock::landing_page_hero_section("Welcome to...", "Subheading"),
50 GutenbergBlock::course_objective_section(),
51 GutenbergBlock::empty_block_from_name("moocfi/course-chapter-grid".to_string()),
52 GutenbergBlock::empty_block_from_name("moocfi/top-level-pages".to_string()),
53 GutenbergBlock::empty_block_from_name("moocfi/congratulations".to_string()),
54 GutenbergBlock::empty_block_from_name("moocfi/course-progress".to_string()),
55 ])?;
56
57 let course_front_page = NewPage {
58 chapter_id: None,
59 content: course_front_page_content,
60 course_id: Some(course.id),
61 exam_id: None,
62 front_page_of_chapter_id: None,
63 title: course.name.clone(),
64 url_path: String::from("/"),
65 exercises: vec![],
66 exercise_slides: vec![],
67 exercise_tasks: vec![],
68 content_search_language: None,
69 };
70 let page = crate::pages::insert_page(
71 &mut tx,
72 course_front_page,
73 user,
74 spec_fetcher,
75 fetch_service_info,
76 )
77 .await?;
78
79 let default_course_instance = crate::course_instances::insert(
81 &mut tx,
82 pkey_policy.map_ref(|x| x.default_course_instance_id),
83 NewCourseInstance {
84 course_id: course.id,
85 name: None,
86 description: None,
87 support_email: None,
88 teacher_in_charge_name: &new_course.teacher_in_charge_name,
89 teacher_in_charge_email: &new_course.teacher_in_charge_email,
90 opening_time: None,
91 closing_time: None,
92 },
93 )
94 .await?;
95
96 let default_module = crate::course_modules::insert(
98 &mut tx,
99 PKeyPolicy::Generate,
100 &NewCourseModule::new_course_default(course.id).set_ects_credits(Some(5.0)),
101 )
102 .await?;
103
104 let peer_or_self_review_config_id =
106 crate::peer_or_self_review_configs::insert(&mut tx, PKeyPolicy::Generate, course.id, None)
107 .await?;
108
109 crate::peer_or_self_review_questions::upsert_multiple_peer_or_self_review_questions(
111 &mut tx,
112 &[
113 CmsPeerOrSelfReviewQuestion {
114 id: Uuid::new_v4(),
115 peer_or_self_review_config_id,
116 order_number: 0,
117 question: "General comments".to_string(),
118 question_type:
119 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Essay,
120 answer_required: false,
121 weight: 0.0,
122 },
123 CmsPeerOrSelfReviewQuestion {
124 id: Uuid::new_v4(),
125 peer_or_self_review_config_id,
126 order_number: 1,
127 question: "The answer was correct".to_string(),
128 question_type:
129 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Scale,
130 answer_required: true,
131 weight: 0.0,
132 },
133 CmsPeerOrSelfReviewQuestion {
134 id: Uuid::new_v4(),
135 peer_or_self_review_config_id,
136 order_number: 2,
137 question: "The answer was easy to read".to_string(),
138 question_type:
139 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Scale,
140 answer_required: true,
141 weight: 0.0,
142 },
143 ],
144 )
145 .await?;
146
147 tx.commit().await?;
148 Ok((course, page, default_course_instance, default_module))
149}
150
151pub async fn create_new_chapter(
153 conn: &mut PgConnection,
154 pkey_policy: PKeyPolicy<(Uuid, Uuid)>,
155 new_chapter: &NewChapter,
156 user: Uuid,
157 spec_fetcher: impl SpecFetcher,
158 fetch_service_info: impl Fn(Url) -> BoxFuture<'static, ModelResult<ExerciseServiceInfoApi>>,
159) -> ModelResult<(DatabaseChapter, Page)> {
160 let mut tx = conn.begin().await?;
161 let chapter_id = chapters::insert(&mut tx, pkey_policy.map_ref(|x| x.0), new_chapter).await?;
162 let chapter = chapters::get_chapter(&mut tx, chapter_id).await?;
163
164 let chapter_frontpage_content = serde_json::to_value(vec![
165 GutenbergBlock::hero_section(&chapter.name, ""),
166 GutenbergBlock::empty_block_from_name("moocfi/pages-in-chapter".to_string()),
167 GutenbergBlock::empty_block_from_name("moocfi/exercises-in-chapter".to_string()),
168 ])?;
169 let chapter_frontpage = NewPage {
170 chapter_id: Some(chapter.id),
171 content: chapter_frontpage_content,
172 course_id: Some(chapter.course_id),
173 exam_id: None,
174 front_page_of_chapter_id: Some(chapter.id),
175 title: chapter.name.clone(),
176 url_path: format!("/chapter-{}", chapter.chapter_number),
177 exercises: vec![],
178 exercise_slides: vec![],
179 exercise_tasks: vec![],
180 content_search_language: None,
181 };
182 let page = pages::insert_page(
183 &mut tx,
184 chapter_frontpage,
185 user,
186 spec_fetcher,
187 fetch_service_info,
188 )
189 .await?;
190 tx.commit().await?;
191 Ok((chapter, page))
192}