Skip to main content

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