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