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::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    // Create default course instance
80    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    // Create default course module
97    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    // Create course default peer review config
105    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    // Create peer review questions for default peer review config
110    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
151/// Creates a new chapter with a front page.
152pub 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}