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_agent_changesets, seed_cs_course_material, seed_glossary,
27            seed_graded_course, 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_agent_changesets::seed_agent_changesets_course(
257            app_config.clone(),
258            Uuid::parse_str("5b0e2a5c-4a26-4c55-9a0c-6f0f8c4f6a31")?,
259            "Agent changesets",
260            "agent-changesets",
261            cs_data.clone(),
262            seed_users_result,
263        )),
264        run_parallelly(seed_glossary::seed_glossary_course(
265            app_config.clone(),
266            Uuid::parse_str("c218ca00-dbde-4b0c-ab98-4f075c49425a")?,
267            "Glossary course",
268            "glossary-course",
269            cs_data.clone(),
270            seed_users_result,
271        )),
272        run_parallelly(seed_sample_course(
273            app_config.clone(),
274            Uuid::parse_str("a2002fc3-2c87-4aae-a5e5-9d14617aad2b")?,
275            "Permission management",
276            "permission-management",
277            cs_data.clone(),
278            false,
279            seed_users_result,
280        )),
281        run_parallelly(seed_sample_course(
282            app_config.clone(),
283            Uuid::parse_str("f9579c00-d0bb-402b-affd-7db330dcb11f")?,
284            "Redirections",
285            "redirections",
286            cs_data.clone(),
287            false,
288            seed_users_result,
289        )),
290        run_parallelly(seed_sample_course(
291            app_config.clone(),
292            Uuid::parse_str("9da60c66-9517-46e4-b351-07d0f7aa6cd4")?,
293            "Limited tries",
294            "limited-tries",
295            cs_data.clone(),
296            false,
297            seed_users_result,
298        )),
299        run_parallelly(seed_sample_course(
300            app_config.clone(),
301            Uuid::parse_str("86cbc198-601c-42f4-8e0f-3e6cce49bbfc")?,
302            "Course Structure",
303            "course-structure",
304            cs_data.clone(),
305            false,
306            seed_users_result,
307        )),
308        run_parallelly(seed_peer_review_course_without_submissions(
309            app_config.clone(),
310            Uuid::parse_str("c47e1cfd-a2da-4fd1-aca8-f2b2d906c4c0")?,
311            "Peer review Course",
312            "peer-review-course",
313            cs_data.clone(),
314        )),
315        run_parallelly(seed_sample_course(
316            app_config.clone(),
317            Uuid::parse_str("557040ea-31bc-47ae-81bd-caeec45a08d0")?,
318            "TestMyCode",
319            "tmc-course",
320            cs_data.clone(),
321            false,
322            seed_users_result
323        )),
324    )?;
325
326    // configure automatic completions
327    let automatic_default_module =
328        course_modules::get_default_by_course_id(&mut conn, automatic_completions_id).await?;
329    let automatic_default_module = course_modules::update_automatic_completion_status(
330        &mut conn,
331        automatic_default_module.id,
332        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
333            course_module_id: automatic_default_module.id,
334            number_of_exercises_attempted_treshold: Some(1),
335            number_of_points_treshold: Some(1),
336            requires_exam: false,
337        }),
338    )
339    .await?;
340    course_modules::update_uh_course_code(
341        &mut conn,
342        automatic_default_module.id,
343        Some("EXAMPLE123".to_string()),
344    )
345    .await?;
346    let automatic_with_exam_default_module =
347        course_modules::get_default_by_course_id(&mut conn, automatic_course_with_exam_id).await?;
348    let automatic_with_exam_default_module = course_modules::update_automatic_completion_status(
349        &mut conn,
350        automatic_with_exam_default_module.id,
351        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
352            course_module_id: automatic_with_exam_default_module.id,
353            number_of_exercises_attempted_treshold: Some(1),
354            number_of_points_treshold: Some(1),
355            requires_exam: true,
356        }),
357    )
358    .await?;
359    course_modules::update_uh_course_code(
360        &mut conn,
361        automatic_with_exam_default_module.id,
362        Some("EXAMPLE123".to_string()),
363    )
364    .await?;
365    open_university_registration_links::upsert(&mut conn, "EXAMPLE123", "https://www.example.com")
366        .await?;
367
368    // configure manual completions
369    let manual_default_module =
370        course_modules::get_default_by_course_id(&mut conn, manual_completions_id).await?;
371    let manual_default_instance =
372        course_instances::get_default_by_course_id(&mut conn, manual_completions_id).await?;
373    library::progressing::add_manual_completions(
374        &mut conn,
375        teacher_user_id,
376        &manual_default_instance,
377        &TeacherManualCompletionRequest {
378            course_module_id: manual_default_module.id,
379            new_completions: vec![TeacherManualCompletion {
380                user_id: *example_normal_user_ids.first().unwrap(),
381                grade: Some(4),
382                passed: true,
383                completion_date: Some(Utc.with_ymd_and_hms(2022, 9, 1, 0, 0, 0).unwrap()),
384            }],
385            skip_duplicate_completions: true,
386        },
387    )
388    .await?;
389
390    // configure certification
391    let certificates_default_module =
392        course_modules::get_default_by_course_id(&mut conn, certificates_id).await?;
393    course_modules::update_automatic_completion_status(
394        &mut conn,
395        certificates_default_module.id,
396        &CompletionPolicy::Automatic(AutomaticCompletionRequirements {
397            course_module_id: certificates_default_module.id,
398            number_of_exercises_attempted_treshold: Some(1),
399            number_of_points_treshold: Some(1),
400            requires_exam: false,
401        }),
402    )
403    .await?;
404    course_modules::update_certification_enabled(
405        &mut conn,
406        certificates_default_module.id,
407        true,
408        teacher_user_id,
409    )
410    .await?;
411
412    roles::insert(
413        &mut conn,
414        language_teacher_user_id,
415        UserRole::Teacher,
416        RoleDomain::Course(introduction_to_localizing),
417    )
418    .await?;
419    roles::insert(
420        &mut conn,
421        language_teacher_user_id,
422        UserRole::Teacher,
423        RoleDomain::Organization(uh_cs_organization_id),
424    )
425    .await?;
426    roles::insert(
427        &mut conn,
428        course_or_exam_creator_user_id,
429        UserRole::CourseOrExamCreator,
430        RoleDomain::Organization(uh_cs_organization_id),
431    )
432    .await?;
433
434    info!("inserting sample exams");
435    create_exam(
436        &mut conn,
437        "Ongoing ends soon".to_string(),
438        Some(Utc::now()),
439        Some(Utc::now() + Duration::minutes(1)),
440        120,
441        uh_cs_organization_id,
442        cs_intro,
443        Uuid::parse_str("7d6ed843-2a94-445b-8ced-ab3c67290ad0")?,
444        teacher_user_id,
445        0,
446        false,
447    )
448    .await?;
449    create_exam(
450        &mut conn,
451        "Ongoing short timer".to_string(),
452        Some(Utc::now()),
453        Some(Utc::now() + Duration::minutes(120)),
454        1,
455        uh_cs_organization_id,
456        cs_intro,
457        Uuid::parse_str("6959e7af-6b78-4d37-b381-eef5b7aaad6c")?,
458        teacher_user_id,
459        0,
460        false,
461    )
462    .await?;
463    create_exam(
464        &mut conn,
465        "Ongoing plenty of time".to_string(),
466        Some(Utc::now()),
467        Some(Utc::now() + Duration::minutes(120)),
468        730,
469        uh_cs_organization_id,
470        cs_intro,
471        Uuid::parse_str("8e202d37-3a26-4181-b9e4-0560b90c0ccb")?,
472        teacher_user_id,
473        0,
474        false,
475    )
476    .await?;
477
478    create_exam(
479        &mut conn,
480        "Exam for manual grading".to_string(),
481        Some(Utc::now()),
482        Some(Utc::now() + Duration::minutes(120)),
483        1,
484        uh_cs_organization_id,
485        cs_intro,
486        Uuid::parse_str("fee8bb0c-8629-477c-86eb-1785005143ae")?,
487        teacher_user_id,
488        0,
489        true,
490    )
491    .await?;
492    create_exam(
493        &mut conn,
494        "Starting soon".to_string(),
495        Some(Utc::now() + Duration::minutes(5)),
496        Some(Utc::now() + Duration::days(30)),
497        1,
498        uh_cs_organization_id,
499        cs_intro,
500        Uuid::parse_str("65f5c3f3-b5fd-478d-8858-a45cdcb16b86")?,
501        teacher_user_id,
502        0,
503        false,
504    )
505    .await?;
506    create_exam(
507        &mut conn,
508        "Over".to_string(),
509        Some(Utc::now() - Duration::days(7)),
510        Some(Utc::now() - Duration::minutes(30)),
511        1,
512        uh_cs_organization_id,
513        cs_intro,
514        Uuid::parse_str("5c4fca1f-f0d6-471f-a0fd-eac552f5fb84")?,
515        teacher_user_id,
516        0,
517        false,
518    )
519    .await?;
520    let automatic_course_exam = create_exam(
521        &mut conn,
522        "Automatic course exam".to_string(),
523        Some(Utc::now()),
524        Some(Utc::now() + Duration::minutes(120)),
525        1,
526        uh_cs_organization_id,
527        cs_intro,
528        Uuid::parse_str("b2168b2f-f721-4771-a35d-ca75ca0937b1")?,
529        teacher_user_id,
530        0,
531        false,
532    )
533    .await?;
534    course_exams::upsert(
535        &mut conn,
536        automatic_course_exam,
537        automatic_course_with_exam_id,
538    )
539    .await?;
540
541    info!("cs");
542    let _cs_design = seed_cs_course_material(
543        &db_pool,
544        &app_config,
545        uh_cs_organization_id,
546        teacher_user_id,
547        langs_user_id,
548        base_url,
549    )
550    .await?;
551    let new_course = NewCourse {
552        name: "Introduction to Computer Science".to_string(),
553        slug: "introduction-to-computer-science".to_string(),
554        organization_id: uh_cs_organization_id,
555        language_code: "en".to_string(),
556        teacher_in_charge_name: "admin".to_string(),
557        teacher_in_charge_email: "admin@example.com".to_string(),
558        description: "An example course.".to_string(),
559        is_draft: false,
560        is_test_mode: false,
561        is_unlisted: false,
562        copy_user_permissions: false,
563        is_joinable_by_code_only: false,
564        join_code: None,
565        ask_marketing_consent: false,
566        flagged_answers_threshold: Some(3),
567        can_add_chatbot: false,
568    };
569    let (cs_course, _cs_front_page, _cs_default_course_instance, _cs_default_course_module) =
570        library::content_management::create_new_course(
571            &mut conn,
572            &app_config,
573            PKeyPolicy::Fixed(CreateNewCourseFixedIds {
574                course_id: Uuid::parse_str("06a7ccbd-8958-4834-918f-ad7b24e583fd")?,
575                default_course_instance_id: Uuid::parse_str(
576                    "48399008-6523-43c5-8fd6-59ecc731a426",
577                )?,
578            }),
579            new_course,
580            teacher_user_id,
581            get_seed_spec_fetcher(),
582            models_requests::fetch_service_info,
583        )
584        .await?;
585    courses::set_cheater_detection_enabled(&mut conn, cs_course.id, false).await?;
586    let _cs_course_instance = course_instances::insert(
587        &mut conn,
588        PKeyPolicy::Fixed(Uuid::parse_str("49c618d3-926d-4287-9159-b3af1f86082d")?),
589        NewCourseInstance {
590            course_id: cs_course.id,
591            name: Some("Non-default instance"),
592            description: Some("This is another non-default instance"),
593            support_email: Some("contact@example.com"),
594            teacher_in_charge_name: "admin",
595            teacher_in_charge_email: "admin@example.com",
596            opening_time: None,
597            closing_time: None,
598        },
599    )
600    .await?;
601    Ok(SeedOrganizationUhCsResult {
602        uh_cs_organization_id,
603        cs_intro_course_id: cs_intro,
604    })
605}