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