headless_lms_models/library/
content_management.rs

1use 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
24/// Creates a new course with a front page and default instances.
25pub 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    // Create front page for course
48    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    // Create default course instance
81    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    // Create default course module
98    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    // Create course default peer review config
106    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    // Create peer review questions for default peer review config
111    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
152/// Creates a new chapter with a front page.
153pub 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}