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 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 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 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 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 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 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 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}