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