headless_lms_server/programs/seed/seed_organizations/
uh_cs.rs

1use std::sync::Arc;
2
3use chrono::{Duration, TimeZone, Utc};
4use futures::try_join;
5use headless_lms_models::{
6    PKeyPolicy, course_exams,
7    course_instances::{self, NewCourseInstance},
8    course_modules::{self, AutomaticCompletionRequirements, CompletionPolicy},
9    courses::NewCourse,
10    library::{
11        self,
12        content_management::CreateNewCourseFixedIds,
13        progressing::{TeacherManualCompletion, TeacherManualCompletionRequest},
14    },
15    open_university_registration_links, organizations,
16    roles::{self, RoleDomain, UserRole},
17};
18use headless_lms_utils::futures::run_parallelly;
19use uuid::Uuid;
20
21use crate::{
22    domain::models_requests::{self, JwtKey},
23    programs::seed::{
24        seed_courses::{
25            CommonCourseData, create_glossary_course, seed_cs_course_material,
26            seed_peer_review_course_without_submissions, seed_sample_course,
27        },
28        seed_file_storage::SeedFileStorageResult,
29        seed_helpers::{create_exam, get_seed_spec_fetcher},
30    },
31};
32
33use super::super::seed_users::SeedUsersResult;
34use sqlx::{Pool, Postgres};
35
36pub struct SeedOrganizationUhCsResult {
37    pub uh_cs_organization_id: Uuid,
38    pub cs_intro_course_id: Uuid,
39}
40
41pub async fn seed_organization_uh_cs(
42    db_pool: Pool<Postgres>,
43    seed_users_result: SeedUsersResult,
44    base_url: String,
45    jwt_key: Arc<JwtKey>,
46    // Passed to this function to ensure the seed file storage has been ran before this. This function will not work is seed file storage has not been ran
47    seed_file_storage_result: SeedFileStorageResult,
48) -> anyhow::Result<SeedOrganizationUhCsResult> {
49    info!("inserting organization uh-cs");
50    let SeedUsersResult {
51        admin_user_id: _,
52        teacher_user_id,
53        language_teacher_user_id,
54        material_viewer_user_id,
55        assistant_user_id: _,
56        course_or_exam_creator_user_id,
57        example_normal_user_ids,
58        teaching_and_learning_services_user_id: _,
59        student_without_research_consent: _,
60        student_without_country: _,
61        user_user_id: _,
62        student_1_user_id: _,
63        student_2_user_id: _,
64        student_3_user_id,
65        student_4_user_id: _,
66        student_5_user_id: _,
67        student_6_user_id: _,
68        langs_user_id,
69        sign_up_user: _,
70    } = seed_users_result;
71    let _ = seed_file_storage_result;
72
73    let mut conn = db_pool.acquire().await?;
74
75    let uh_cs_organization_id = organizations::insert(
76        &mut conn,
77        PKeyPolicy::Fixed(Uuid::parse_str("8bb12295-53ac-4099-9644-ac0ff5e34d92")?),
78        "University of Helsinki, Department of Computer Science",
79        "uh-cs",
80        Some("Organization for Computer Science students and the rest of the world who wish to learn the basics in Computer Science, programming and software development."),
81        false,
82    )
83    .await?;
84
85    roles::insert(
86        &mut conn,
87        material_viewer_user_id,
88        UserRole::MaterialViewer,
89        RoleDomain::Organization(uh_cs_organization_id),
90    )
91    .await?;
92
93    info!("inserting uh-cs courses");
94
95    // Seed courses in groups to improve performance. We cannot create a new task for each course because it is causing stack overflows in headless-lms entrypoint in seemingly unrelated code.
96    let cs_data = CommonCourseData {
97        db_pool: db_pool.clone(),
98        organization_id: uh_cs_organization_id,
99        teacher_user_id,
100        student_user_id: student_3_user_id,
101        langs_user_id,
102        example_normal_user_ids: Arc::new(example_normal_user_ids.to_vec()),
103        jwt_key: Arc::clone(&jwt_key),
104        base_url: base_url.clone(),
105    };
106    let (
107        cs_intro,
108        automatic_completions_id,
109        introduction_to_localizing,
110        manual_completions_id,
111        automatic_course_with_exam_id,
112        certificates_id,
113        ..,
114    ) = try_join!(
115        // using these ids
116        run_parallelly(seed_sample_course(
117            Uuid::parse_str("7f36cf71-c2d2-41fc-b2ae-bbbcafab0ea5")?,
118            "Introduction to everything",
119            "introduction-to-everything",
120            cs_data.clone(),
121            false,
122            seed_users_result,
123        )),
124        run_parallelly(seed_sample_course(
125            Uuid::parse_str("b39b64f3-7718-4556-ac2b-333f3ed4096f")?,
126            "Automatic Completions",
127            "automatic-completions",
128            cs_data.clone(),
129            false,
130            seed_users_result,
131        )),
132        run_parallelly(seed_sample_course(
133            Uuid::parse_str("639f4d25-9376-49b5-bcca-7cba18c38565")?,
134            "Introduction to localizing",
135            "introduction-to-localizing",
136            cs_data.clone(),
137            false,
138            seed_users_result,
139        )),
140        run_parallelly(seed_sample_course(
141            Uuid::parse_str("34f4e7b7-9f55-48a7-95d7-3fc3e89553b5")?,
142            "Manual Completions",
143            "manual-completions",
144            cs_data.clone(),
145            false,
146            seed_users_result,
147        )),
148        run_parallelly(seed_sample_course(
149            Uuid::parse_str("260b2157-94ad-4791-91c7-f236f203c338")?,
150            "Automatic Course with Exam",
151            "automatic-course-with-exam",
152            cs_data.clone(),
153            false,
154            seed_users_result,
155        )),
156        run_parallelly(seed_sample_course(
157            Uuid::parse_str("51ce5ea4-2587-407e-bea9-421309f77f69")?,
158            "Certificates",
159            "certificates",
160            cs_data.clone(),
161            false,
162            seed_users_result,
163        )),
164        // not using these ids
165        run_parallelly(seed_sample_course(
166            Uuid::parse_str("4dde368a-5e5d-4001-b8aa-13079390f818")?,
167            "Model solutions",
168            "model-solutions",
169            cs_data.clone(),
170            false,
171            seed_users_result,
172        )),
173        run_parallelly(seed_sample_course(
174            Uuid::parse_str("edaa1c52-15cd-458d-8ce2-1e4010641244")?,
175            "Course Modules",
176            "course-modules",
177            cs_data.clone(),
178            false,
179            seed_users_result,
180        )),
181        run_parallelly(seed_sample_course(
182            Uuid::parse_str("d18b3780-563d-4326-b311-8d0e132901cd")?,
183            "Introduction to feedback",
184            "introduction-to-feedback",
185            cs_data.clone(),
186            false,
187            seed_users_result,
188        )),
189        run_parallelly(seed_sample_course(
190            Uuid::parse_str("0ab2c4c5-3aad-4daa-a8fe-c26e956fde35")?,
191            "Introduction to history",
192            "introduction-to-history",
193            cs_data.clone(),
194            false,
195            seed_users_result,
196        )),
197        run_parallelly(seed_sample_course(
198            Uuid::parse_str("cae7da38-9486-47da-9106-bff9b6a280f2")?,
199            "Introduction to edit proposals",
200            "introduction-to-edit-proposals",
201            cs_data.clone(),
202            false,
203            seed_users_result,
204        )),
205        run_parallelly(seed_sample_course(
206            Uuid::parse_str("b4cb334c-11d6-4e93-8f3d-849c4abfcd67")?,
207            "Point view for teachers",
208            "point-view-for-teachers",
209            cs_data.clone(),
210            false,
211            seed_users_result,
212        )),
213        run_parallelly(seed_sample_course(
214            Uuid::parse_str("1e0c52c7-8cb9-4089-b1c3-c24fc0dd5ae4")?,
215            "Advanced course instance management",
216            "advanced-course-instance-management",
217            cs_data.clone(),
218            false,
219            seed_users_result,
220        )),
221        run_parallelly(seed_sample_course(
222            Uuid::parse_str("0cf67777-0edb-480c-bdb6-13f90c136fc3")?,
223            "Advanced exercise states",
224            "advanced-exercise-states",
225            cs_data.clone(),
226            false,
227            seed_users_result,
228        )),
229        run_parallelly(seed_sample_course(
230            Uuid::parse_str("c218ca00-dbde-4b0c-ab98-4f075c49425a")?,
231            "Glossary course",
232            "glossary-course",
233            cs_data.clone(),
234            false,
235            seed_users_result,
236        )),
237        run_parallelly(seed_sample_course(
238            Uuid::parse_str("a2002fc3-2c87-4aae-a5e5-9d14617aad2b")?,
239            "Permission management",
240            "permission-management",
241            cs_data.clone(),
242            false,
243            seed_users_result,
244        )),
245        run_parallelly(seed_sample_course(
246            Uuid::parse_str("f9579c00-d0bb-402b-affd-7db330dcb11f")?,
247            "Redirections",
248            "redirections",
249            cs_data.clone(),
250            false,
251            seed_users_result,
252        )),
253        run_parallelly(seed_sample_course(
254            Uuid::parse_str("9da60c66-9517-46e4-b351-07d0f7aa6cd4")?,
255            "Limited tries",
256            "limited-tries",
257            cs_data.clone(),
258            false,
259            seed_users_result,
260        )),
261        run_parallelly(seed_sample_course(
262            Uuid::parse_str("86cbc198-601c-42f4-8e0f-3e6cce49bbfc")?,
263            "Course Structure",
264            "course-structure",
265            cs_data.clone(),
266            false,
267            seed_users_result,
268        )),
269        run_parallelly(create_glossary_course(
270            Uuid::parse_str("e5b89931-e3d6-4930-9692-61539748c12c")?,
271            cs_data.clone(),
272        )),
273        run_parallelly(seed_peer_review_course_without_submissions(
274            Uuid::parse_str("c47e1cfd-a2da-4fd1-aca8-f2b2d906c4c0")?,
275            "Peer review Course",
276            "peer-review-course",
277            cs_data.clone(),
278        )),
279        run_parallelly(seed_sample_course(
280            Uuid::parse_str("557040ea-31bc-47ae-81bd-caeec45a08d0")?,
281            "TestMyCode",
282            "tmc-course",
283            cs_data.clone(),
284            false,
285            seed_users_result
286        )),
287    )?;
288
289    // configure automatic completions
290    let automatic_default_module =
291        course_modules::get_default_by_course_id(&mut conn, automatic_completions_id).await?;
292    let automatic_default_module = course_modules::update_automatic_completion_status(
293        &mut conn,
294        automatic_default_module.id,
295        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
296            course_module_id: automatic_default_module.id,
297            number_of_exercises_attempted_treshold: Some(1),
298            number_of_points_treshold: Some(1),
299            requires_exam: false,
300        }),
301    )
302    .await?;
303    course_modules::update_uh_course_code(
304        &mut conn,
305        automatic_default_module.id,
306        Some("EXAMPLE123".to_string()),
307    )
308    .await?;
309    let automatic_with_exam_default_module =
310        course_modules::get_default_by_course_id(&mut conn, automatic_course_with_exam_id).await?;
311    let automatic_with_exam_default_module = course_modules::update_automatic_completion_status(
312        &mut conn,
313        automatic_with_exam_default_module.id,
314        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
315            course_module_id: automatic_with_exam_default_module.id,
316            number_of_exercises_attempted_treshold: Some(1),
317            number_of_points_treshold: Some(1),
318            requires_exam: true,
319        }),
320    )
321    .await?;
322    course_modules::update_uh_course_code(
323        &mut conn,
324        automatic_with_exam_default_module.id,
325        Some("EXAMPLE123".to_string()),
326    )
327    .await?;
328    open_university_registration_links::upsert(&mut conn, "EXAMPLE123", "https://www.example.com")
329        .await?;
330
331    // configure manual completions
332    let manual_default_module =
333        course_modules::get_default_by_course_id(&mut conn, manual_completions_id).await?;
334    let manual_default_instance =
335        course_instances::get_default_by_course_id(&mut conn, manual_completions_id).await?;
336    library::progressing::add_manual_completions(
337        &mut conn,
338        teacher_user_id,
339        &manual_default_instance,
340        &TeacherManualCompletionRequest {
341            course_module_id: manual_default_module.id,
342            new_completions: vec![TeacherManualCompletion {
343                user_id: *example_normal_user_ids.first().unwrap(),
344                grade: Some(4),
345                passed: true,
346                completion_date: Some(Utc.with_ymd_and_hms(2022, 9, 1, 0, 0, 0).unwrap()),
347            }],
348            skip_duplicate_completions: true,
349        },
350    )
351    .await?;
352
353    // configure certification
354    let certificates_default_module =
355        course_modules::get_default_by_course_id(&mut conn, certificates_id).await?;
356    course_modules::update_automatic_completion_status(
357        &mut conn,
358        certificates_default_module.id,
359        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
360            course_module_id: certificates_default_module.id,
361            number_of_exercises_attempted_treshold: Some(1),
362            number_of_points_treshold: Some(1),
363            requires_exam: false,
364        }),
365    )
366    .await?;
367    course_modules::update_certification_enabled(&mut conn, certificates_default_module.id, true)
368        .await?;
369
370    roles::insert(
371        &mut conn,
372        language_teacher_user_id,
373        UserRole::Teacher,
374        RoleDomain::Course(introduction_to_localizing),
375    )
376    .await?;
377    roles::insert(
378        &mut conn,
379        course_or_exam_creator_user_id,
380        UserRole::CourseOrExamCreator,
381        RoleDomain::Organization(uh_cs_organization_id),
382    )
383    .await?;
384
385    info!("inserting sample exams");
386    create_exam(
387        &mut conn,
388        "Ongoing ends soon".to_string(),
389        Some(Utc::now()),
390        Some(Utc::now() + Duration::minutes(1)),
391        120,
392        uh_cs_organization_id,
393        cs_intro,
394        Uuid::parse_str("7d6ed843-2a94-445b-8ced-ab3c67290ad0")?,
395        teacher_user_id,
396        0,
397        false,
398    )
399    .await?;
400    create_exam(
401        &mut conn,
402        "Ongoing short timer".to_string(),
403        Some(Utc::now()),
404        Some(Utc::now() + Duration::minutes(120)),
405        1,
406        uh_cs_organization_id,
407        cs_intro,
408        Uuid::parse_str("6959e7af-6b78-4d37-b381-eef5b7aaad6c")?,
409        teacher_user_id,
410        0,
411        false,
412    )
413    .await?;
414    create_exam(
415        &mut conn,
416        "Ongoing plenty of time".to_string(),
417        Some(Utc::now()),
418        Some(Utc::now() + Duration::minutes(120)),
419        730,
420        uh_cs_organization_id,
421        cs_intro,
422        Uuid::parse_str("8e202d37-3a26-4181-b9e4-0560b90c0ccb")?,
423        teacher_user_id,
424        0,
425        false,
426    )
427    .await?;
428
429    create_exam(
430        &mut conn,
431        "Exam for manual grading".to_string(),
432        Some(Utc::now()),
433        Some(Utc::now() + Duration::minutes(120)),
434        1,
435        uh_cs_organization_id,
436        cs_intro,
437        Uuid::parse_str("fee8bb0c-8629-477c-86eb-1785005143ae")?,
438        teacher_user_id,
439        0,
440        true,
441    )
442    .await?;
443    create_exam(
444        &mut conn,
445        "Starting soon".to_string(),
446        Some(Utc::now() + Duration::minutes(5)),
447        Some(Utc::now() + Duration::days(30)),
448        1,
449        uh_cs_organization_id,
450        cs_intro,
451        Uuid::parse_str("65f5c3f3-b5fd-478d-8858-a45cdcb16b86")?,
452        teacher_user_id,
453        0,
454        false,
455    )
456    .await?;
457    create_exam(
458        &mut conn,
459        "Over".to_string(),
460        Some(Utc::now() - Duration::days(7)),
461        Some(Utc::now() - Duration::minutes(30)),
462        1,
463        uh_cs_organization_id,
464        cs_intro,
465        Uuid::parse_str("5c4fca1f-f0d6-471f-a0fd-eac552f5fb84")?,
466        teacher_user_id,
467        0,
468        false,
469    )
470    .await?;
471    let automatic_course_exam = create_exam(
472        &mut conn,
473        "Automatic course exam".to_string(),
474        Some(Utc::now()),
475        Some(Utc::now() + Duration::minutes(120)),
476        1,
477        uh_cs_organization_id,
478        cs_intro,
479        Uuid::parse_str("b2168b2f-f721-4771-a35d-ca75ca0937b1")?,
480        teacher_user_id,
481        0,
482        false,
483    )
484    .await?;
485    course_exams::upsert(
486        &mut conn,
487        automatic_course_exam,
488        automatic_course_with_exam_id,
489    )
490    .await?;
491
492    info!("cs");
493    let _cs_design = seed_cs_course_material(
494        &db_pool,
495        uh_cs_organization_id,
496        teacher_user_id,
497        langs_user_id,
498        base_url,
499    )
500    .await?;
501    let new_course = NewCourse {
502        name: "Introduction to Computer Science".to_string(),
503        slug: "introduction-to-computer-science".to_string(),
504        organization_id: uh_cs_organization_id,
505        language_code: "en-US".to_string(),
506        teacher_in_charge_name: "admin".to_string(),
507        teacher_in_charge_email: "admin@example.com".to_string(),
508        description: "An example course.".to_string(),
509        is_draft: false,
510        is_test_mode: false,
511        is_unlisted: false,
512        copy_user_permissions: false,
513        is_joinable_by_code_only: false,
514        join_code: None,
515        ask_marketing_consent: false,
516        flagged_answers_threshold: Some(3),
517        can_add_chatbot: false,
518    };
519    let (cs_course, _cs_front_page, _cs_default_course_instance, _cs_default_course_module) =
520        library::content_management::create_new_course(
521            &mut conn,
522            PKeyPolicy::Fixed(CreateNewCourseFixedIds {
523                course_id: Uuid::parse_str("06a7ccbd-8958-4834-918f-ad7b24e583fd")?,
524                default_course_instance_id: Uuid::parse_str(
525                    "48399008-6523-43c5-8fd6-59ecc731a426",
526                )?,
527            }),
528            new_course,
529            teacher_user_id,
530            get_seed_spec_fetcher(),
531            models_requests::fetch_service_info,
532        )
533        .await?;
534    let _cs_course_instance = course_instances::insert(
535        &mut conn,
536        PKeyPolicy::Fixed(Uuid::parse_str("49c618d3-926d-4287-9159-b3af1f86082d")?),
537        NewCourseInstance {
538            course_id: cs_course.id,
539            name: Some("Non-default instance"),
540            description: Some("This is another non-default instance"),
541            support_email: Some("contact@example.com"),
542            teacher_in_charge_name: "admin",
543            teacher_in_charge_email: "admin@example.com",
544            opening_time: None,
545            closing_time: None,
546        },
547    )
548    .await?;
549    Ok(SeedOrganizationUhCsResult {
550        uh_cs_organization_id,
551        cs_intro_course_id: cs_intro,
552    })
553}