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::landing_page_copy_text("About this course", "This course teaches you xxx."),
51 GutenbergBlock::course_objective_section(),
52 GutenbergBlock::empty_block_from_name("moocfi/course-chapter-grid".to_string()),
53 GutenbergBlock::empty_block_from_name("moocfi/top-level-pages".to_string()),
54 GutenbergBlock::empty_block_from_name("moocfi/congratulations".to_string()),
55 GutenbergBlock::empty_block_from_name("moocfi/course-progress".to_string()),
56 ])?;
57
58 let course_front_page = NewPage {
59 chapter_id: None,
60 content: course_front_page_content,
61 course_id: Some(course.id),
62 exam_id: None,
63 front_page_of_chapter_id: None,
64 title: course.name.clone(),
65 url_path: String::from("/"),
66 exercises: vec![],
67 exercise_slides: vec![],
68 exercise_tasks: vec![],
69 content_search_language: None,
70 };
71 let page = crate::pages::insert_page(
72 &mut tx,
73 course_front_page,
74 user,
75 spec_fetcher,
76 fetch_service_info,
77 )
78 .await?;
79
80 let default_course_instance = crate::course_instances::insert(
82 &mut tx,
83 pkey_policy.map_ref(|x| x.default_course_instance_id),
84 NewCourseInstance {
85 course_id: course.id,
86 name: None,
87 description: None,
88 support_email: None,
89 teacher_in_charge_name: &new_course.teacher_in_charge_name,
90 teacher_in_charge_email: &new_course.teacher_in_charge_email,
91 opening_time: None,
92 closing_time: None,
93 },
94 )
95 .await?;
96
97 let default_module = crate::course_modules::insert(
99 &mut tx,
100 PKeyPolicy::Generate,
101 &NewCourseModule::new_course_default(course.id).set_ects_credits(Some(5.0)),
102 )
103 .await?;
104
105 let peer_or_self_review_config_id =
107 crate::peer_or_self_review_configs::insert(&mut tx, PKeyPolicy::Generate, course.id, None)
108 .await?;
109
110 crate::peer_or_self_review_questions::upsert_multiple_peer_or_self_review_questions(
112 &mut tx,
113 &[
114 CmsPeerOrSelfReviewQuestion {
115 id: Uuid::new_v4(),
116 peer_or_self_review_config_id,
117 order_number: 0,
118 question: "General comments".to_string(),
119 question_type:
120 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Essay,
121 answer_required: false,
122 weight: 0.0,
123 },
124 CmsPeerOrSelfReviewQuestion {
125 id: Uuid::new_v4(),
126 peer_or_self_review_config_id,
127 order_number: 1,
128 question: "The answer was correct".to_string(),
129 question_type:
130 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Scale,
131 answer_required: true,
132 weight: 0.0,
133 },
134 CmsPeerOrSelfReviewQuestion {
135 id: Uuid::new_v4(),
136 peer_or_self_review_config_id,
137 order_number: 2,
138 question: "The answer was easy to read".to_string(),
139 question_type:
140 crate::peer_or_self_review_questions::PeerOrSelfReviewQuestionType::Scale,
141 answer_required: true,
142 weight: 0.0,
143 },
144 ],
145 )
146 .await?;
147
148 tx.commit().await?;
149 Ok((course, page, default_course_instance, default_module))
150}
151
152pub async fn create_new_chapter(
154 conn: &mut PgConnection,
155 pkey_policy: PKeyPolicy<(Uuid, Uuid)>,
156 new_chapter: &NewChapter,
157 user: Uuid,
158 spec_fetcher: impl SpecFetcher,
159 fetch_service_info: impl Fn(Url) -> BoxFuture<'static, ModelResult<ExerciseServiceInfoApi>>,
160) -> ModelResult<(DatabaseChapter, Page)> {
161 let mut tx = conn.begin().await?;
162 let chapter_id = chapters::insert(&mut tx, pkey_policy.map_ref(|x| x.0), new_chapter).await?;
163 let chapter = chapters::get_chapter(&mut tx, chapter_id).await?;
164
165 let chapter_frontpage_content = serde_json::to_value(vec![
166 GutenbergBlock::hero_section(&chapter.name, ""),
167 GutenbergBlock::empty_block_from_name("moocfi/pages-in-chapter".to_string()),
168 GutenbergBlock::empty_block_from_name("moocfi/exercises-in-chapter".to_string()),
169 ])?;
170 let chapter_frontpage = NewPage {
171 chapter_id: Some(chapter.id),
172 content: chapter_frontpage_content,
173 course_id: Some(chapter.course_id),
174 exam_id: None,
175 front_page_of_chapter_id: Some(chapter.id),
176 title: chapter.name.clone(),
177 url_path: format!("/chapter-{}", chapter.chapter_number),
178 exercises: vec![],
179 exercise_slides: vec![],
180 exercise_tasks: vec![],
181 content_search_language: None,
182 };
183 let page = pages::insert_page(
184 &mut tx,
185 chapter_frontpage,
186 user,
187 spec_fetcher,
188 fetch_service_info,
189 )
190 .await?;
191 tx.commit().await?;
192 Ok((chapter, page))
193}