1pub mod chatbots;
4pub mod stats;
5pub mod students;
6
7use chrono::Utc;
8use domain::csv_export::user_exercise_states_export::UserExerciseStatesExportOperation;
9use headless_lms_models::{
10 partner_block::PartnersBlock,
11 suspected_cheaters::{SuspectedCheaters, Threshold},
12};
13use rand::Rng;
14use std::sync::Arc;
15
16use headless_lms_utils::strings::is_ietf_language_code_like;
17use models::{
18 chapters::Chapter,
19 course_instances::{CourseInstance, CourseInstanceForm, NewCourseInstance},
20 course_module_completions::CourseModuleCompletion,
21 course_modules::ModuleUpdates,
22 courses::{Course, CourseBreadcrumbInfo, CourseStructure, CourseUpdate, NewCourse},
23 exercise_slide_submissions::{
24 self, ExerciseAnswersInCourseRequiringAttentionCount, ExerciseSlideSubmissionCount,
25 ExerciseSlideSubmissionCountByExercise, ExerciseSlideSubmissionCountByWeekAndHour,
26 },
27 exercises::{Exercise, ExerciseStatusSummaryForUser},
28 feedback::{self, Feedback, FeedbackCount},
29 glossary::{Term, TermUpdate},
30 library,
31 material_references::{MaterialReference, NewMaterialReference},
32 page_visit_datum_summary_by_courses::PageVisitDatumSummaryByCourse,
33 page_visit_datum_summary_by_courses_countries::PageVisitDatumSummaryByCoursesCountries,
34 page_visit_datum_summary_by_courses_device_types::PageVisitDatumSummaryByCourseDeviceTypes,
35 page_visit_datum_summary_by_pages::PageVisitDatumSummaryByPages,
36 pages::Page,
37 peer_or_self_review_configs::PeerOrSelfReviewConfig,
38 peer_or_self_review_questions::PeerOrSelfReviewQuestion,
39 user_course_settings::UserCourseSettings,
40 user_exercise_states::{ExerciseUserCounts, UserCourseProgress},
41};
42
43use crate::{
44 domain::models_requests::{self, JwtKey},
45 prelude::*,
46};
47
48use headless_lms_models::course_language_groups;
49
50use crate::domain::csv_export::course_instance_export::CourseInstancesExportOperation;
51use crate::domain::csv_export::course_research_form_questions_answers_export::CourseResearchFormExportOperation;
52use crate::domain::csv_export::exercise_tasks_export::CourseExerciseTasksExportOperation;
53use crate::domain::csv_export::general_export;
54use crate::domain::csv_export::submissions::CourseSubmissionExportOperation;
55use crate::domain::csv_export::users_export::UsersExportOperation;
56
57#[instrument(skip(pool))]
61async fn get_course(
62 course_id: web::Path<Uuid>,
63 pool: web::Data<PgPool>,
64 user: AuthUser,
65) -> ControllerResult<web::Json<Course>> {
66 let mut conn = pool.acquire().await?;
67 let token = authorize_access_to_course_material(&mut conn, Some(user.id), *course_id).await?;
68 let course = models::courses::get_course(&mut conn, *course_id).await?;
69 token.authorized_ok(web::Json(course))
70}
71
72#[instrument(skip(pool))]
76async fn get_course_breadcrumb_info(
77 course_id: web::Path<Uuid>,
78 pool: web::Data<PgPool>,
79 user: AuthUser,
80) -> ControllerResult<web::Json<CourseBreadcrumbInfo>> {
81 let mut conn = pool.acquire().await?;
82 let user_id = Some(user.id);
83 let token = authorize_access_to_course_material(&mut conn, user_id, *course_id).await?;
84 let info = models::courses::get_course_breadcrumb_info(&mut conn, *course_id).await?;
85 token.authorized_ok(web::Json(info))
86}
87
88#[instrument(skip(pool))]
92async fn get_all_exercise_statuses_by_course_id(
93 params: web::Path<(Uuid, Uuid)>,
94 pool: web::Data<PgPool>,
95 user: AuthUser,
96) -> ControllerResult<web::Json<Vec<ExerciseStatusSummaryForUser>>> {
97 let (course_id, user_id) = params.into_inner();
98 let mut conn = pool.acquire().await?;
99 let token = authorize(
100 &mut conn,
101 Act::ViewUserProgressOrDetails,
102 Some(user.id),
103 Res::Course(course_id),
104 )
105 .await?;
106 let res = models::exercises::get_all_exercise_statuses_by_user_id_and_course_id(
107 &mut conn, course_id, user_id,
108 )
109 .await?;
110 token.authorized_ok(web::Json(res))
111}
112
113#[instrument(skip(pool))]
117async fn get_all_course_module_completions_for_user_by_course_id(
118 params: web::Path<(Uuid, Uuid)>,
119 pool: web::Data<PgPool>,
120 user: AuthUser,
121) -> ControllerResult<web::Json<Vec<CourseModuleCompletion>>> {
122 let (course_id, user_id) = params.into_inner();
123 let mut conn = pool.acquire().await?;
124 let token = authorize(
125 &mut conn,
126 Act::ViewUserProgressOrDetails,
127 Some(user.id),
128 Res::Course(course_id),
129 )
130 .await?;
131 let res = models::course_module_completions::get_all_by_course_id_and_user_id(
132 &mut conn, course_id, user_id,
133 )
134 .await?;
135 token.authorized_ok(web::Json(res))
136}
137
138#[instrument(skip(pool))]
142async fn get_user_progress_for_course(
143 path: web::Path<(Uuid, Uuid)>,
144 pool: web::Data<PgPool>,
145 user: AuthUser,
146) -> ControllerResult<web::Json<Vec<UserCourseProgress>>> {
147 let (course_id, target_user_id) = path.into_inner();
148 let mut conn = pool.acquire().await?;
149 let token = authorize(
150 &mut conn,
151 Act::ViewUserProgressOrDetails,
152 Some(user.id),
153 Res::Course(course_id),
154 )
155 .await?;
156 let user_course_progress = models::user_exercise_states::get_user_course_progress(
157 &mut conn,
158 course_id,
159 target_user_id,
160 false,
161 )
162 .await?;
163 token.authorized_ok(web::Json(user_course_progress))
164}
165
166#[instrument(skip(pool))]
170async fn get_user_course_settings(
171 path: web::Path<(Uuid, Uuid)>,
172 pool: web::Data<PgPool>,
173 user: AuthUser,
174) -> ControllerResult<web::Json<Option<UserCourseSettings>>> {
175 let (course_id, target_user_id) = path.into_inner();
176 let mut conn = pool.acquire().await?;
177 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
178 let settings = models::user_course_settings::get_user_course_settings_by_course_id(
179 &mut conn,
180 target_user_id,
181 course_id,
182 )
183 .await?;
184 token.authorized_ok(web::Json(settings))
185}
186
187#[instrument(skip(pool, user))]
194async fn post_reprocess_module_completions(
195 pool: web::Data<PgPool>,
196 user: AuthUser,
197 course_id: web::Path<Uuid>,
198) -> ControllerResult<web::Json<bool>> {
199 let mut conn = pool.acquire().await?;
200 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::GlobalPermissions).await?;
201 models::library::progressing::process_all_course_completions(&mut conn, *course_id).await?;
202 token.authorized_ok(web::Json(true))
203}
204
205#[instrument(skip(pool, app_conf))]
223async fn post_new_course(
224 request_id: RequestId,
225 pool: web::Data<PgPool>,
226 payload: web::Json<NewCourse>,
227 user: AuthUser,
228 app_conf: web::Data<ApplicationConfiguration>,
229 jwt_key: web::Data<JwtKey>,
230) -> ControllerResult<web::Json<Course>> {
231 let mut conn = pool.acquire().await?;
232 let new_course = payload.0;
233 if !is_ietf_language_code_like(&new_course.language_code) {
234 return Err(ControllerError::new(
235 ControllerErrorType::BadRequest,
236 "Malformed language code.".to_string(),
237 None,
238 ));
239 }
240 let token = authorize(
241 &mut conn,
242 Act::CreateCoursesOrExams,
243 Some(user.id),
244 Res::Organization(new_course.organization_id),
245 )
246 .await?;
247
248 let mut tx = conn.begin().await?;
249 let (course, ..) = library::content_management::create_new_course(
250 &mut tx,
251 PKeyPolicy::Generate,
252 new_course,
253 user.id,
254 models_requests::make_spec_fetcher(
255 app_conf.base_url.clone(),
256 request_id.0,
257 Arc::clone(&jwt_key),
258 ),
259 models_requests::fetch_service_info,
260 )
261 .await?;
262 models::roles::insert(
263 &mut tx,
264 user.id,
265 models::roles::UserRole::Teacher,
266 models::roles::RoleDomain::Course(course.id),
267 )
268 .await?;
269 tx.commit().await?;
270
271 token.authorized_ok(web::Json(course))
272}
273
274#[instrument(skip(pool))]
290async fn update_course(
291 payload: web::Json<CourseUpdate>,
292 course_id: web::Path<Uuid>,
293 pool: web::Data<PgPool>,
294 user: AuthUser,
295) -> ControllerResult<web::Json<Course>> {
296 let mut conn = pool.acquire().await?;
297 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
298 let course_update = payload.0;
299 let course_before_update = models::courses::get_course(&mut conn, *course_id).await?;
300 if course_update.can_add_chatbot != course_before_update.can_add_chatbot {
301 let _token2 =
303 authorize(&mut conn, Act::Teach, Some(user.id), Res::GlobalPermissions).await?;
304 }
305
306 let locking_just_enabled =
307 !course_before_update.chapter_locking_enabled && course_update.chapter_locking_enabled;
308
309 let course = models::courses::update_course(&mut conn, *course_id, course_update).await?;
310
311 if locking_just_enabled {
312 use models::{chapters, user_chapter_locking_statuses, user_course_settings};
313
314 let all_user_settings =
315 user_course_settings::get_all_by_course_id(&mut conn, *course_id).await?;
316
317 for settings in all_user_settings {
318 let existing_statuses = user_chapter_locking_statuses::get_by_user_and_course(
319 &mut conn,
320 settings.user_id,
321 *course_id,
322 )
323 .await?;
324
325 if existing_statuses.is_empty() {
326 chapters::unlock_first_chapters_for_user(&mut conn, settings.user_id, *course_id)
327 .await?;
328 }
329 }
330 }
331
332 token.authorized_ok(web::Json(course))
333}
334
335#[instrument(skip(pool))]
339async fn delete_course(
340 course_id: web::Path<Uuid>,
341 pool: web::Data<PgPool>,
342 user: AuthUser,
343) -> ControllerResult<web::Json<Course>> {
344 let mut conn = pool.acquire().await?;
345 let token = authorize(
346 &mut conn,
347 Act::UsuallyUnacceptableDeletion,
348 Some(user.id),
349 Res::Course(*course_id),
350 )
351 .await?;
352 let course = models::courses::delete_course(&mut conn, *course_id).await?;
353
354 token.authorized_ok(web::Json(course))
355}
356
357#[instrument(skip(pool, file_store, app_conf))]
405async fn get_course_structure(
406 course_id: web::Path<Uuid>,
407 pool: web::Data<PgPool>,
408 user: AuthUser,
409 file_store: web::Data<dyn FileStore>,
410 app_conf: web::Data<ApplicationConfiguration>,
411) -> ControllerResult<web::Json<CourseStructure>> {
412 let mut conn = pool.acquire().await?;
413 let token = authorize(
414 &mut conn,
415 Act::ViewInternalCourseStructure,
416 Some(user.id),
417 Res::Course(*course_id),
418 )
419 .await?;
420 let course_structure = models::courses::get_course_structure(
421 &mut conn,
422 *course_id,
423 file_store.as_ref(),
424 app_conf.as_ref(),
425 )
426 .await?;
427
428 token.authorized_ok(web::Json(course_structure))
429}
430
431#[instrument(skip(payload, request, pool, file_store, app_conf))]
447async fn add_media_for_course(
448 course_id: web::Path<Uuid>,
449 payload: Multipart,
450 request: HttpRequest,
451 pool: web::Data<PgPool>,
452 user: AuthUser,
453 file_store: web::Data<dyn FileStore>,
454 app_conf: web::Data<ApplicationConfiguration>,
455) -> ControllerResult<web::Json<UploadResult>> {
456 let mut conn = pool.acquire().await?;
457 let course = models::courses::get_course(&mut conn, *course_id).await?;
458 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
459 let media_path = upload_file_from_cms(
460 request.headers(),
461 payload,
462 StoreKind::Course(course.id),
463 file_store.as_ref(),
464 &mut conn,
465 user,
466 )
467 .await?;
468 let download_url = file_store.get_download_url(media_path.as_path(), app_conf.as_ref());
469
470 token.authorized_ok(web::Json(UploadResult { url: download_url }))
471}
472
473#[instrument(skip(pool))]
477async fn get_all_exercises(
478 pool: web::Data<PgPool>,
479 course_id: web::Path<Uuid>,
480 user: AuthUser,
481) -> ControllerResult<web::Json<Vec<Exercise>>> {
482 let mut conn = pool.acquire().await?;
483 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
484 let exercises = models::exercises::get_exercises_by_course_id(&mut conn, *course_id).await?;
485
486 token.authorized_ok(web::Json(exercises))
487}
488
489#[instrument(skip(pool))]
493async fn get_all_exercises_and_count_of_answers_requiring_attention(
494 pool: web::Data<PgPool>,
495 course_id: web::Path<Uuid>,
496 user: AuthUser,
497) -> ControllerResult<web::Json<Vec<ExerciseAnswersInCourseRequiringAttentionCount>>> {
498 let mut conn = pool.acquire().await?;
499 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
500 let _exercises = models::exercises::get_exercises_by_course_id(&mut conn, *course_id).await?;
501 let count_of_answers_requiring_attention = models::exercise_slide_submissions::get_count_of_answers_requiring_attention_in_exercise_by_course_id(&mut conn, *course_id).await?;
502 token.authorized_ok(web::Json(count_of_answers_requiring_attention))
503}
504
505#[instrument(skip(pool))]
517async fn get_all_course_language_versions(
518 pool: web::Data<PgPool>,
519 course_id: web::Path<Uuid>,
520 user: AuthUser,
521) -> ControllerResult<web::Json<Vec<Course>>> {
522 let mut conn = pool.acquire().await?;
523 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
524 let course = models::courses::get_course(&mut conn, *course_id).await?;
525 let language_versions =
526 models::courses::get_all_language_versions_of_course(&mut conn, &course).await?;
527
528 token.authorized_ok(web::Json(language_versions))
529}
530
531#[derive(Deserialize, Debug)]
532#[serde(tag = "mode", rename_all = "snake_case")]
533#[cfg_attr(feature = "ts_rs", derive(TS))]
534pub enum CopyCourseMode {
535 Duplicate,
537 SameLanguageGroup,
539 ExistingLanguageGroup { target_course_id: Uuid },
541 NewLanguageGroup,
543}
544
545#[derive(Deserialize, Debug)]
546#[cfg_attr(feature = "ts_rs", derive(TS))]
547pub struct CopyCourseRequest {
548 #[serde(flatten)]
549 pub new_course: NewCourse,
550 pub mode: CopyCourseMode,
551}
552
553#[instrument(skip(pool))]
597pub async fn create_course_copy(
598 pool: web::Data<PgPool>,
599 course_id: web::Path<Uuid>,
600 payload: web::Json<CopyCourseRequest>,
601 user: AuthUser,
602) -> ControllerResult<web::Json<Course>> {
603 let mut conn = pool.acquire().await?;
604 let token = authorize(
605 &mut conn,
606 Act::Duplicate,
607 Some(user.id),
608 Res::Course(*course_id),
609 )
610 .await?;
611
612 let mut tx = conn.begin().await?;
613
614 let new_course = payload.new_course.clone();
615
616 let copied_course = match &payload.mode {
617 CopyCourseMode::Duplicate => {
618 models::library::copying::copy_course(&mut tx, *course_id, &new_course, false, user.id)
619 .await?
620 }
621 CopyCourseMode::SameLanguageGroup => {
622 models::library::copying::copy_course(&mut tx, *course_id, &new_course, true, user.id)
623 .await?
624 }
625 CopyCourseMode::ExistingLanguageGroup { target_course_id } => {
626 let target_course = models::courses::get_course(&mut tx, *target_course_id).await?;
627 authorize(
629 &mut tx,
630 Act::Duplicate,
631 Some(user.id),
632 Res::Course(*target_course_id),
633 )
634 .await?;
635 models::library::copying::copy_course_with_language_group(
636 &mut tx,
637 *course_id,
638 target_course.course_language_group_id,
639 &new_course,
640 user.id,
641 )
642 .await?
643 }
644 CopyCourseMode::NewLanguageGroup => {
645 let new_clg_id = course_language_groups::insert(
646 &mut tx,
647 PKeyPolicy::Generate,
648 new_course.slug.as_str(),
649 )
650 .await?;
651 models::library::copying::copy_course_with_language_group(
652 &mut tx,
653 *course_id,
654 new_clg_id,
655 &new_course,
656 user.id,
657 )
658 .await?
659 }
660 };
661
662 models::roles::insert(
663 &mut tx,
664 user.id,
665 models::roles::UserRole::Teacher,
666 models::roles::RoleDomain::Course(copied_course.id),
667 )
668 .await?;
669
670 tx.commit().await?;
671
672 token.authorized_ok(web::Json(copied_course))
673}
674
675#[instrument(skip(pool))]
679async fn get_daily_submission_counts(
680 pool: web::Data<PgPool>,
681 course_id: web::Path<Uuid>,
682 user: AuthUser,
683) -> ControllerResult<web::Json<Vec<ExerciseSlideSubmissionCount>>> {
684 let mut conn = pool.acquire().await?;
685 let token = authorize(
686 &mut conn,
687 Act::ViewStats,
688 Some(user.id),
689 Res::Course(*course_id),
690 )
691 .await?;
692 let course = models::courses::get_course(&mut conn, *course_id).await?;
693 let res =
694 exercise_slide_submissions::get_course_daily_slide_submission_counts(&mut conn, &course)
695 .await?;
696
697 token.authorized_ok(web::Json(res))
698}
699
700#[instrument(skip(pool))]
704async fn get_daily_user_counts_with_submissions(
705 pool: web::Data<PgPool>,
706 course_id: web::Path<Uuid>,
707 user: AuthUser,
708) -> ControllerResult<web::Json<Vec<ExerciseSlideSubmissionCount>>> {
709 let mut conn = pool.acquire().await?;
710 let token = authorize(
711 &mut conn,
712 Act::ViewStats,
713 Some(user.id),
714 Res::Course(*course_id),
715 )
716 .await?;
717 let course = models::courses::get_course(&mut conn, *course_id).await?;
718 let res = exercise_slide_submissions::get_course_daily_user_counts_with_submissions(
719 &mut conn, &course,
720 )
721 .await?;
722
723 token.authorized_ok(web::Json(res))
724}
725
726#[instrument(skip(pool))]
730async fn get_weekday_hour_submission_counts(
731 pool: web::Data<PgPool>,
732 course_id: web::Path<Uuid>,
733 user: AuthUser,
734) -> ControllerResult<web::Json<Vec<ExerciseSlideSubmissionCountByWeekAndHour>>> {
735 let mut conn = pool.acquire().await?;
736 let token = authorize(
737 &mut conn,
738 Act::ViewStats,
739 Some(user.id),
740 Res::Course(*course_id),
741 )
742 .await?;
743 let course = models::courses::get_course(&mut conn, *course_id).await?;
744 let res = exercise_slide_submissions::get_course_exercise_slide_submission_counts_by_weekday_and_hour(
745 &mut conn, &course,
746 )
747 .await?;
748
749 token.authorized_ok(web::Json(res))
750}
751
752#[instrument(skip(pool))]
756async fn get_submission_counts_by_exercise(
757 pool: web::Data<PgPool>,
758 course_id: web::Path<Uuid>,
759 user: AuthUser,
760) -> ControllerResult<web::Json<Vec<ExerciseSlideSubmissionCountByExercise>>> {
761 let mut conn = pool.acquire().await?;
762 let token = authorize(
763 &mut conn,
764 Act::ViewStats,
765 Some(user.id),
766 Res::Course(*course_id),
767 )
768 .await?;
769 let course = models::courses::get_course(&mut conn, *course_id).await?;
770 let res = exercise_slide_submissions::get_course_exercise_slide_submission_counts_by_exercise(
771 &mut conn, &course,
772 )
773 .await?;
774
775 token.authorized_ok(web::Json(res))
776}
777
778#[instrument(skip(pool))]
782async fn get_course_instances(
783 pool: web::Data<PgPool>,
784 course_id: web::Path<Uuid>,
785 user: AuthUser,
786) -> ControllerResult<web::Json<Vec<CourseInstance>>> {
787 let mut conn = pool.acquire().await?;
788 let token = authorize(
789 &mut conn,
790 Act::Teach,
791 Some(user.id),
792 Res::Course(*course_id),
793 )
794 .await?;
795 let course_instances =
796 models::course_instances::get_course_instances_for_course(&mut conn, *course_id).await?;
797
798 token.authorized_ok(web::Json(course_instances))
799}
800
801#[derive(Debug, Deserialize)]
802#[cfg_attr(feature = "ts_rs", derive(TS))]
803pub struct GetFeedbackQuery {
804 read: bool,
805 #[serde(flatten)]
806 pagination: Pagination,
807}
808
809#[instrument(skip(pool))]
813pub async fn get_feedback(
814 course_id: web::Path<Uuid>,
815 pool: web::Data<PgPool>,
816 read: web::Query<GetFeedbackQuery>,
817 user: AuthUser,
818) -> ControllerResult<web::Json<Vec<Feedback>>> {
819 let mut conn = pool.acquire().await?;
820 let token = authorize(
821 &mut conn,
822 Act::Teach,
823 Some(user.id),
824 Res::Course(*course_id),
825 )
826 .await?;
827 let feedback =
828 feedback::get_feedback_for_course(&mut conn, *course_id, read.read, read.pagination)
829 .await?;
830
831 token.authorized_ok(web::Json(feedback))
832}
833
834#[instrument(skip(pool))]
838pub async fn get_feedback_count(
839 course_id: web::Path<Uuid>,
840 pool: web::Data<PgPool>,
841 user: AuthUser,
842) -> ControllerResult<web::Json<FeedbackCount>> {
843 let mut conn = pool.acquire().await?;
844 let token = authorize(
845 &mut conn,
846 Act::Teach,
847 Some(user.id),
848 Res::Course(*course_id),
849 )
850 .await?;
851
852 let feedback_count = feedback::get_feedback_count_for_course(&mut conn, *course_id).await?;
853
854 token.authorized_ok(web::Json(feedback_count))
855}
856
857#[instrument(skip(pool))]
861async fn new_course_instance(
862 form: web::Json<CourseInstanceForm>,
863 course_id: web::Path<Uuid>,
864 pool: web::Data<PgPool>,
865 user: AuthUser,
866) -> ControllerResult<web::Json<Uuid>> {
867 let mut conn = pool.acquire().await?;
868 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
869 let form = form.into_inner();
870 let new = NewCourseInstance {
871 course_id: *course_id,
872 name: form.name.as_deref(),
873 description: form.description.as_deref(),
874 support_email: form.support_email.as_deref(),
875 teacher_in_charge_name: &form.teacher_in_charge_name,
876 teacher_in_charge_email: &form.teacher_in_charge_email,
877 opening_time: form.opening_time,
878 closing_time: form.closing_time,
879 };
880 let ci = models::course_instances::insert(&mut conn, PKeyPolicy::Generate, new).await?;
881
882 token.authorized_ok(web::Json(ci.id))
883}
884
885#[instrument(skip(pool))]
886async fn glossary(
887 pool: web::Data<PgPool>,
888 course_id: web::Path<Uuid>,
889 user: AuthUser,
890) -> ControllerResult<web::Json<Vec<Term>>> {
891 let mut conn = pool.acquire().await?;
892 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
893 let glossary = models::glossary::fetch_for_course(&mut conn, *course_id).await?;
894
895 token.authorized_ok(web::Json(glossary))
896}
897
898#[instrument(skip(pool))]
901async fn _new_term(
902 pool: web::Data<PgPool>,
903 course_id: web::Path<Uuid>,
904 user: AuthUser,
905) -> ControllerResult<web::Json<Vec<Term>>> {
906 let mut conn = pool.acquire().await?;
907 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
908 let glossary = models::glossary::fetch_for_course(&mut conn, *course_id).await?;
909
910 token.authorized_ok(web::Json(glossary))
911}
912
913#[instrument(skip(pool))]
914async fn new_glossary_term(
915 pool: web::Data<PgPool>,
916 course_id: web::Path<Uuid>,
917 new_term: web::Json<TermUpdate>,
918 user: AuthUser,
919) -> ControllerResult<web::Json<Uuid>> {
920 let mut conn = pool.acquire().await?;
921 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
922 let TermUpdate { term, definition } = new_term.into_inner();
923 let term = models::glossary::insert(&mut conn, &term, &definition, *course_id).await?;
924
925 token.authorized_ok(web::Json(term))
926}
927
928#[instrument(skip(pool))]
932pub async fn get_course_users_counts_by_exercise(
933 course_id: web::Path<Uuid>,
934 pool: web::Data<PgPool>,
935 user: AuthUser,
936) -> ControllerResult<web::Json<Vec<ExerciseUserCounts>>> {
937 let mut conn = pool.acquire().await?;
938 let course_id = course_id.into_inner();
939 let token = authorize(
940 &mut conn,
941 Act::ViewStats,
942 Some(user.id),
943 Res::Course(course_id),
944 )
945 .await?;
946
947 let res =
948 models::user_exercise_states::get_course_users_counts_by_exercise(&mut conn, course_id)
949 .await?;
950
951 token.authorized_ok(web::Json(res))
952}
953
954#[instrument(skip(pool))]
962pub async fn post_new_page_ordering(
963 course_id: web::Path<Uuid>,
964 pool: web::Data<PgPool>,
965 user: AuthUser,
966 payload: web::Json<Vec<Page>>,
967) -> ControllerResult<web::Json<()>> {
968 let mut conn = pool.acquire().await?;
969 let course_id = course_id.into_inner();
970 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
971
972 models::pages::reorder_pages(&mut conn, &payload, course_id).await?;
973
974 token.authorized_ok(web::Json(()))
975}
976
977#[instrument(skip(pool))]
983pub async fn post_new_chapter_ordering(
984 course_id: web::Path<Uuid>,
985 pool: web::Data<PgPool>,
986 user: AuthUser,
987 payload: web::Json<Vec<Chapter>>,
988) -> ControllerResult<web::Json<()>> {
989 let mut conn = pool.acquire().await?;
990 let course_id = course_id.into_inner();
991 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
992
993 models::pages::reorder_chapters(&mut conn, &payload, course_id).await?;
994
995 token.authorized_ok(web::Json(()))
996}
997
998#[instrument(skip(pool))]
999async fn get_material_references_by_course_id(
1000 course_id: web::Path<Uuid>,
1001 pool: web::Data<PgPool>,
1002 user: AuthUser,
1003) -> ControllerResult<web::Json<Vec<MaterialReference>>> {
1004 let mut conn = pool.acquire().await?;
1005 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
1006
1007 let res =
1008 models::material_references::get_references_by_course_id(&mut conn, *course_id).await?;
1009 token.authorized_ok(web::Json(res))
1010}
1011
1012#[instrument(skip(pool))]
1013async fn insert_material_references(
1014 course_id: web::Path<Uuid>,
1015 payload: web::Json<Vec<NewMaterialReference>>,
1016 pool: web::Data<PgPool>,
1017 user: AuthUser,
1018) -> ControllerResult<web::Json<()>> {
1019 let mut conn = pool.acquire().await?;
1020 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
1021
1022 models::material_references::insert_reference(&mut conn, *course_id, payload.0).await?;
1023
1024 token.authorized_ok(web::Json(()))
1025}
1026
1027#[instrument(skip(pool))]
1028async fn update_material_reference(
1029 path: web::Path<(Uuid, Uuid)>,
1030 pool: web::Data<PgPool>,
1031 user: AuthUser,
1032 payload: web::Json<NewMaterialReference>,
1033) -> ControllerResult<web::Json<()>> {
1034 let (course_id, reference_id) = path.into_inner();
1035 let mut conn = pool.acquire().await?;
1036 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
1037
1038 models::material_references::update_material_reference_by_id(
1039 &mut conn,
1040 reference_id,
1041 payload.0,
1042 )
1043 .await?;
1044 token.authorized_ok(web::Json(()))
1045}
1046
1047#[instrument(skip(pool))]
1048async fn delete_material_reference_by_id(
1049 path: web::Path<(Uuid, Uuid)>,
1050 pool: web::Data<PgPool>,
1051 user: AuthUser,
1052) -> ControllerResult<web::Json<()>> {
1053 let (course_id, reference_id) = path.into_inner();
1054 let mut conn = pool.acquire().await?;
1055 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(course_id)).await?;
1056
1057 models::material_references::delete_reference(&mut conn, reference_id).await?;
1058 token.authorized_ok(web::Json(()))
1059}
1060
1061#[instrument(skip(pool))]
1062pub async fn update_modules(
1063 course_id: web::Path<Uuid>,
1064 pool: web::Data<PgPool>,
1065 user: AuthUser,
1066 payload: web::Json<ModuleUpdates>,
1067) -> ControllerResult<web::Json<()>> {
1068 let mut conn = pool.acquire().await?;
1069 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
1070
1071 models::course_modules::update_modules(&mut conn, *course_id, payload.into_inner()).await?;
1072 token.authorized_ok(web::Json(()))
1073}
1074
1075async fn get_course_default_peer_review(
1076 course_id: web::Path<Uuid>,
1077 pool: web::Data<PgPool>,
1078 user: AuthUser,
1079) -> ControllerResult<web::Json<(PeerOrSelfReviewConfig, Vec<PeerOrSelfReviewQuestion>)>> {
1080 let mut conn = pool.acquire().await?;
1081 let token = authorize(
1082 &mut conn,
1083 Act::Teach,
1084 Some(user.id),
1085 Res::Course(*course_id),
1086 )
1087 .await?;
1088
1089 let peer_review = models::peer_or_self_review_configs::get_default_for_course_by_course_id(
1090 &mut conn, *course_id,
1091 )
1092 .await?;
1093 let peer_or_self_review_questions =
1094 models::peer_or_self_review_questions::get_all_by_peer_or_self_review_config_id(
1095 &mut conn,
1096 peer_review.id,
1097 )
1098 .await?;
1099 token.authorized_ok(web::Json((peer_review, peer_or_self_review_questions)))
1100}
1101
1102#[instrument(skip(pool, user))]
1109async fn post_update_peer_review_queue_reviews_received(
1110 pool: web::Data<PgPool>,
1111 user: AuthUser,
1112 course_id: web::Path<Uuid>,
1113) -> ControllerResult<web::Json<bool>> {
1114 let mut conn = pool.acquire().await?;
1115 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::GlobalPermissions).await?;
1116 models::library::peer_or_self_reviewing::update_peer_review_queue_reviews_received(
1117 &mut conn, *course_id,
1118 )
1119 .await?;
1120 token.authorized_ok(web::Json(true))
1121}
1122
1123#[instrument(skip(pool))]
1129pub async fn submission_export(
1130 course_id: web::Path<Uuid>,
1131 pool: web::Data<PgPool>,
1132 user: AuthUser,
1133) -> ControllerResult<HttpResponse> {
1134 let mut conn = pool.acquire().await?;
1135
1136 let token = authorize(
1137 &mut conn,
1138 Act::Teach,
1139 Some(user.id),
1140 Res::Course(*course_id),
1141 )
1142 .await?;
1143
1144 let course = models::courses::get_course(&mut conn, *course_id).await?;
1145
1146 general_export(
1147 pool,
1148 &format!(
1149 "attachment; filename=\"Course: {} - Submissions (exercise tasks) {}.csv\"",
1150 course.name,
1151 Utc::now().format("%Y-%m-%d")
1152 ),
1153 CourseSubmissionExportOperation {
1154 course_id: *course_id,
1155 },
1156 token,
1157 )
1158 .await
1159}
1160
1161#[instrument(skip(pool))]
1167pub async fn user_details_export(
1168 course_id: web::Path<Uuid>,
1169 pool: web::Data<PgPool>,
1170 user: AuthUser,
1171) -> ControllerResult<HttpResponse> {
1172 let mut conn = pool.acquire().await?;
1173
1174 let token = authorize(
1175 &mut conn,
1176 Act::Teach,
1177 Some(user.id),
1178 Res::Course(*course_id),
1179 )
1180 .await?;
1181
1182 let course = models::courses::get_course(&mut conn, *course_id).await?;
1183
1184 general_export(
1185 pool,
1186 &format!(
1187 "attachment; filename=\"Course: {} - User Details {}.csv\"",
1188 course.name,
1189 Utc::now().format("%Y-%m-%d")
1190 ),
1191 UsersExportOperation {
1192 course_id: *course_id,
1193 },
1194 token,
1195 )
1196 .await
1197}
1198
1199#[instrument(skip(pool))]
1205pub async fn exercise_tasks_export(
1206 course_id: web::Path<Uuid>,
1207 pool: web::Data<PgPool>,
1208 user: AuthUser,
1209) -> ControllerResult<HttpResponse> {
1210 let mut conn = pool.acquire().await?;
1211
1212 let token = authorize(
1213 &mut conn,
1214 Act::Teach,
1215 Some(user.id),
1216 Res::Course(*course_id),
1217 )
1218 .await?;
1219
1220 let course = models::courses::get_course(&mut conn, *course_id).await?;
1221
1222 general_export(
1223 pool,
1224 &format!(
1225 "attachment; filename=\"Course: {} - Exercise tasks {}.csv\"",
1226 course.name,
1227 Utc::now().format("%Y-%m-%d")
1228 ),
1229 CourseExerciseTasksExportOperation {
1230 course_id: *course_id,
1231 },
1232 token,
1233 )
1234 .await
1235}
1236
1237#[instrument(skip(pool))]
1243pub async fn course_instances_export(
1244 course_id: web::Path<Uuid>,
1245 pool: web::Data<PgPool>,
1246 user: AuthUser,
1247) -> ControllerResult<HttpResponse> {
1248 let mut conn = pool.acquire().await?;
1249
1250 let token = authorize(
1251 &mut conn,
1252 Act::Teach,
1253 Some(user.id),
1254 Res::Course(*course_id),
1255 )
1256 .await?;
1257
1258 let course = models::courses::get_course(&mut conn, *course_id).await?;
1259
1260 general_export(
1261 pool,
1262 &format!(
1263 "attachment; filename=\"Course: {} - Instances {}.csv\"",
1264 course.name,
1265 Utc::now().format("%Y-%m-%d")
1266 ),
1267 CourseInstancesExportOperation {
1268 course_id: *course_id,
1269 },
1270 token,
1271 )
1272 .await
1273}
1274
1275#[instrument(skip(pool))]
1281pub async fn course_consent_form_answers_export(
1282 course_id: web::Path<Uuid>,
1283 pool: web::Data<PgPool>,
1284 user: AuthUser,
1285) -> ControllerResult<HttpResponse> {
1286 let mut conn = pool.acquire().await?;
1287
1288 let token = authorize(
1289 &mut conn,
1290 Act::Teach,
1291 Some(user.id),
1292 Res::Course(*course_id),
1293 )
1294 .await?;
1295
1296 let course = models::courses::get_course(&mut conn, *course_id).await?;
1297
1298 general_export(
1299 pool,
1300 &format!(
1301 "attachment; filename=\"Course: {} - User Consents {}.csv\"",
1302 course.name,
1303 Utc::now().format("%Y-%m-%d")
1304 ),
1305 CourseResearchFormExportOperation {
1306 course_id: *course_id,
1307 },
1308 token,
1309 )
1310 .await
1311}
1312
1313#[instrument(skip(pool))]
1319pub async fn user_exercise_states_export(
1320 course_id: web::Path<Uuid>,
1321 pool: web::Data<PgPool>,
1322 user: AuthUser,
1323) -> ControllerResult<HttpResponse> {
1324 let mut conn = pool.acquire().await?;
1325
1326 let token = authorize(
1327 &mut conn,
1328 Act::Teach,
1329 Some(user.id),
1330 Res::Course(*course_id),
1331 )
1332 .await?;
1333
1334 let course = models::courses::get_course(&mut conn, *course_id).await?;
1335
1336 general_export(
1337 pool,
1338 &format!(
1339 "attachment; filename=\"Course: {} - User exercise states {}.csv\"",
1340 course.name,
1341 Utc::now().format("%Y-%m-%d")
1342 ),
1343 UserExerciseStatesExportOperation {
1344 course_id: *course_id,
1345 },
1346 token,
1347 )
1348 .await
1349}
1350
1351pub async fn get_page_visit_datum_summary(
1355 course_id: web::Path<Uuid>,
1356 pool: web::Data<PgPool>,
1357 user: AuthUser,
1358) -> ControllerResult<web::Json<Vec<PageVisitDatumSummaryByCourse>>> {
1359 let mut conn = pool.acquire().await?;
1360 let course_id = course_id.into_inner();
1361 let token = authorize(
1362 &mut conn,
1363 Act::ViewStats,
1364 Some(user.id),
1365 Res::Course(course_id),
1366 )
1367 .await?;
1368
1369 let res = models::page_visit_datum_summary_by_courses::get_all_for_course(&mut conn, course_id)
1370 .await?;
1371
1372 token.authorized_ok(web::Json(res))
1373}
1374
1375pub async fn get_page_visit_datum_summary_by_pages(
1379 course_id: web::Path<Uuid>,
1380 pool: web::Data<PgPool>,
1381 user: AuthUser,
1382) -> ControllerResult<web::Json<Vec<PageVisitDatumSummaryByPages>>> {
1383 let mut conn = pool.acquire().await?;
1384 let course_id = course_id.into_inner();
1385 let token = authorize(
1386 &mut conn,
1387 Act::ViewStats,
1388 Some(user.id),
1389 Res::Course(course_id),
1390 )
1391 .await?;
1392
1393 let res =
1394 models::page_visit_datum_summary_by_pages::get_all_for_course(&mut conn, course_id).await?;
1395
1396 token.authorized_ok(web::Json(res))
1397}
1398
1399pub async fn get_page_visit_datum_summary_by_device_types(
1403 course_id: web::Path<Uuid>,
1404 pool: web::Data<PgPool>,
1405 user: AuthUser,
1406) -> ControllerResult<web::Json<Vec<PageVisitDatumSummaryByCourseDeviceTypes>>> {
1407 let mut conn = pool.acquire().await?;
1408 let course_id = course_id.into_inner();
1409 let token = authorize(
1410 &mut conn,
1411 Act::ViewStats,
1412 Some(user.id),
1413 Res::Course(course_id),
1414 )
1415 .await?;
1416
1417 let res = models::page_visit_datum_summary_by_courses_device_types::get_all_for_course(
1418 &mut conn, course_id,
1419 )
1420 .await?;
1421
1422 token.authorized_ok(web::Json(res))
1423}
1424
1425pub async fn get_page_visit_datum_summary_by_countries(
1429 course_id: web::Path<Uuid>,
1430 pool: web::Data<PgPool>,
1431 user: AuthUser,
1432) -> ControllerResult<web::Json<Vec<PageVisitDatumSummaryByCoursesCountries>>> {
1433 let mut conn = pool.acquire().await?;
1434 let course_id = course_id.into_inner();
1435 let token = authorize(
1436 &mut conn,
1437 Act::ViewStats,
1438 Some(user.id),
1439 Res::Course(course_id),
1440 )
1441 .await?;
1442
1443 let res = models::page_visit_datum_summary_by_courses_countries::get_all_for_course(
1444 &mut conn, course_id,
1445 )
1446 .await?;
1447
1448 token.authorized_ok(web::Json(res))
1449}
1450
1451pub async fn teacher_reset_course_progress_for_themselves(
1457 course_id: web::Path<Uuid>,
1458 pool: web::Data<PgPool>,
1459 user: AuthUser,
1460) -> ControllerResult<web::Json<bool>> {
1461 let mut conn = pool.acquire().await?;
1462 let course_id = course_id.into_inner();
1463 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1464
1465 let mut tx = conn.begin().await?;
1466 let course_instances =
1467 models::course_instances::get_course_instances_for_course(&mut tx, course_id).await?;
1468 for course_instance in course_instances {
1469 models::course_instances::reset_progress_on_course_instance_for_user(
1470 &mut tx,
1471 user.id,
1472 course_instance.course_id,
1473 )
1474 .await?;
1475 }
1476
1477 tx.commit().await?;
1478 token.authorized_ok(web::Json(true))
1479}
1480
1481pub async fn teacher_reset_course_progress_for_everyone(
1487 course_id: web::Path<Uuid>,
1488 pool: web::Data<PgPool>,
1489 user: AuthUser,
1490) -> ControllerResult<web::Json<bool>> {
1491 let mut conn = pool.acquire().await?;
1492 let course_id = course_id.into_inner();
1493 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1494 let course = models::courses::get_course(&mut conn, course_id).await?;
1495 if !course.is_draft {
1496 return Err(ControllerError::new(
1497 ControllerErrorType::BadRequest,
1498 "Can only reset progress for a draft course.".to_string(),
1499 None,
1500 ));
1501 }
1502 let n_course_module_completions =
1504 models::course_module_completions::get_count_of_distinct_completors_by_course_id(
1505 &mut conn, course_id,
1506 )
1507 .await?;
1508 let n_completions_registered_to_study_registry = models::course_module_completion_registered_to_study_registries::get_count_of_distinct_users_with_registrations_by_course_id(
1509 &mut conn, course_id,
1510 ).await?;
1511 if n_course_module_completions > 200 {
1512 return Err(ControllerError::new(
1513 ControllerErrorType::BadRequest,
1514 "Too many students have completed the course.".to_string(),
1515 None,
1516 ));
1517 }
1518 if n_completions_registered_to_study_registry > 2 {
1519 return Err(ControllerError::new(
1520 ControllerErrorType::BadRequest,
1521 "Too many students have registered their completion to a study registry".to_string(),
1522 None,
1523 ));
1524 }
1525
1526 let mut tx = conn.begin().await?;
1527 let course_instances =
1528 models::course_instances::get_course_instances_for_course(&mut tx, course_id).await?;
1529
1530 for course_instance in course_instances {
1532 let users_in_course_instance =
1533 models::users::get_users_by_course_instance_enrollment(&mut tx, course_instance.id)
1534 .await?;
1535 for user_in_course_instance in users_in_course_instance {
1536 models::course_instances::reset_progress_on_course_instance_for_user(
1537 &mut tx,
1538 user_in_course_instance.id,
1539 course_instance.course_id,
1540 )
1541 .await?;
1542 }
1543 }
1544
1545 tx.commit().await?;
1546 token.authorized_ok(web::Json(true))
1547}
1548
1549#[derive(Debug, Deserialize)]
1550#[cfg_attr(feature = "ts_rs", derive(TS))]
1551pub struct GetSuspectedCheatersQuery {
1552 archive: bool,
1553}
1554
1555#[instrument(skip(pool))]
1559async fn get_all_suspected_cheaters(
1560 user: AuthUser,
1561 params: web::Path<Uuid>,
1562 query: web::Query<GetSuspectedCheatersQuery>,
1563 pool: web::Data<PgPool>,
1564) -> ControllerResult<web::Json<Vec<SuspectedCheaters>>> {
1565 let course_id = params.into_inner();
1566
1567 let mut conn = pool.acquire().await?;
1568 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1569
1570 let course_cheaters = models::suspected_cheaters::get_all_suspected_cheaters_in_course(
1571 &mut conn,
1572 course_id,
1573 query.archive,
1574 )
1575 .await?;
1576
1577 token.authorized_ok(web::Json(course_cheaters))
1578}
1579
1580#[instrument(skip(pool))]
1584async fn get_all_thresholds(
1585 user: AuthUser,
1586 params: web::Path<Uuid>,
1587 pool: web::Data<PgPool>,
1588) -> ControllerResult<web::Json<Vec<Threshold>>> {
1589 let mut conn = pool.acquire().await?;
1590 let course_id = params.into_inner();
1591
1592 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1593
1594 let thresholds =
1595 models::suspected_cheaters::get_all_thresholds_for_course(&mut conn, course_id).await?;
1596
1597 token.authorized_ok(web::Json(thresholds))
1598}
1599
1600#[instrument(skip(pool))]
1604async fn teacher_archive_suspected_cheater(
1605 user: AuthUser,
1606 path: web::Path<(Uuid, Uuid)>,
1607 pool: web::Data<PgPool>,
1608) -> ControllerResult<web::Json<()>> {
1609 let (course_id, user_id) = path.into_inner();
1610
1611 let mut conn = pool.acquire().await?;
1612 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1613
1614 models::suspected_cheaters::archive_suspected_cheater(&mut conn, user_id).await?;
1615
1616 token.authorized_ok(web::Json(()))
1617}
1618
1619#[instrument(skip(pool))]
1623async fn teacher_approve_suspected_cheater(
1624 user: AuthUser,
1625 path: web::Path<(Uuid, Uuid)>,
1626 pool: web::Data<PgPool>,
1627) -> ControllerResult<web::Json<()>> {
1628 let (course_id, user_id) = path.into_inner();
1629
1630 let mut conn = pool.acquire().await?;
1631 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1632
1633 models::suspected_cheaters::approve_suspected_cheater(&mut conn, user_id).await?;
1634
1635 models::course_module_completions::update_passed_and_grade_status(
1638 &mut conn, course_id, user_id, false, 0,
1639 )
1640 .await?;
1641
1642 token.authorized_ok(web::Json(()))
1643}
1644
1645#[instrument(skip(pool))]
1649async fn add_user_to_course_with_join_code(
1650 course_id: web::Path<Uuid>,
1651 user: AuthUser,
1652 pool: web::Data<PgPool>,
1653) -> ControllerResult<web::Json<Uuid>> {
1654 let mut conn = pool.acquire().await?;
1655 let token = skip_authorize();
1656
1657 let joined =
1658 models::join_code_uses::insert(&mut conn, PKeyPolicy::Generate, user.id, *course_id)
1659 .await?;
1660 token.authorized_ok(web::Json(joined))
1661}
1662
1663#[instrument(skip(pool))]
1667async fn set_join_code_for_course(
1668 id: web::Path<Uuid>,
1669 pool: web::Data<PgPool>,
1670 user: AuthUser,
1671) -> ControllerResult<HttpResponse> {
1672 let mut conn = pool.acquire().await?;
1673 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*id)).await?;
1674
1675 const CHARSET: &[u8] = b"ABCDEFGHJKMNPQRSTUVWXYZ\
1676 abcdefghjkmnpqrstuvwxyz";
1677 const PASSWORD_LEN: usize = 64;
1678 let mut rng = rand::rng();
1679
1680 let code: String = (0..PASSWORD_LEN)
1681 .map(|_| {
1682 let idx = rng.random_range(0..CHARSET.len());
1683 CHARSET[idx] as char
1684 })
1685 .collect();
1686
1687 models::courses::set_join_code_for_course(&mut conn, *id, code).await?;
1688 token.authorized_ok(HttpResponse::Ok().finish())
1689}
1690
1691#[instrument(skip(pool))]
1695async fn get_course_with_join_code(
1696 join_code: web::Path<String>,
1697 user: AuthUser,
1698 pool: web::Data<PgPool>,
1699) -> ControllerResult<web::Json<Course>> {
1700 let mut conn = pool.acquire().await?;
1701 let token = skip_authorize();
1702 let course =
1703 models::courses::get_course_with_join_code(&mut conn, join_code.to_string()).await?;
1704
1705 token.authorized_ok(web::Json(course))
1706}
1707
1708#[instrument(skip(payload, pool))]
1712async fn post_partners_block(
1713 path: web::Path<Uuid>,
1714 payload: web::Json<Option<serde_json::Value>>,
1715 pool: web::Data<PgPool>,
1716 user: AuthUser,
1717) -> ControllerResult<web::Json<PartnersBlock>> {
1718 let course_id = path.into_inner();
1719
1720 let content = payload.into_inner();
1721 let mut conn = pool.acquire().await?;
1722 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1723
1724 let upserted_partner_block =
1725 models::partner_block::upsert_partner_block(&mut conn, course_id, content).await?;
1726
1727 token.authorized_ok(web::Json(upserted_partner_block))
1728}
1729
1730#[instrument(skip(pool))]
1734async fn get_partners_block(
1735 path: web::Path<Uuid>,
1736 user: AuthUser,
1737 pool: web::Data<PgPool>,
1738) -> ControllerResult<web::Json<PartnersBlock>> {
1739 let course_id = path.into_inner();
1740 let mut conn = pool.acquire().await?;
1741 let token = authorize(&mut conn, Act::Teach, Some(user.id), Res::Course(course_id)).await?;
1742
1743 let course_exists = models::partner_block::check_if_course_exists(&mut conn, course_id).await?;
1745
1746 let partner_block = if course_exists {
1747 models::partner_block::get_partner_block(&mut conn, course_id).await?
1749 } else {
1750 let empty_content: Option<serde_json::Value> = Some(serde_json::Value::Array(vec![]));
1752
1753 models::partner_block::upsert_partner_block(&mut conn, course_id, empty_content).await?
1755 };
1756
1757 token.authorized_ok(web::Json(partner_block))
1758}
1759
1760#[instrument(skip(pool))]
1764async fn delete_partners_block(
1765 path: web::Path<Uuid>,
1766 pool: web::Data<PgPool>,
1767 user: AuthUser,
1768) -> ControllerResult<web::Json<PartnersBlock>> {
1769 let course_id = path.into_inner();
1770 let mut conn = pool.acquire().await?;
1771 let token = authorize(
1772 &mut conn,
1773 Act::UsuallyUnacceptableDeletion,
1774 Some(user.id),
1775 Res::Course(course_id),
1776 )
1777 .await?;
1778 let deleted_partners_block =
1779 models::partner_block::delete_partner_block(&mut conn, course_id).await?;
1780
1781 token.authorized_ok(web::Json(deleted_partners_block))
1782}
1783
1784pub fn _add_routes(cfg: &mut ServiceConfig) {
1792 cfg.service(web::scope("/{course_id}/stats").configure(stats::_add_routes))
1793 .service(web::scope("/{course_id}/chatbots").configure(chatbots::_add_routes))
1794 .service(web::scope("/{course_id}/students").configure(students::_add_routes))
1795 .route("/{course_id}", web::get().to(get_course))
1796 .route("", web::post().to(post_new_course))
1797 .route("/{course_id}", web::put().to(update_course))
1798 .route("/{course_id}", web::delete().to(delete_course))
1799 .route(
1800 "/{course_id}/status-for-all-exercises/{user_id}",
1801 web::get().to(get_all_exercise_statuses_by_course_id),
1802 )
1803 .route(
1804 "/{course_id}/course-module-completions/{user_id}",
1805 web::get().to(get_all_course_module_completions_for_user_by_course_id),
1806 )
1807 .route(
1808 "/{course_id}/daily-submission-counts",
1809 web::get().to(get_daily_submission_counts),
1810 )
1811 .route(
1812 "/{course_id}/daily-users-who-have-submitted-something",
1813 web::get().to(get_daily_user_counts_with_submissions),
1814 )
1815 .route("/{course_id}/exercises", web::get().to(get_all_exercises))
1816 .route(
1817 "/{course_id}/exercises-and-count-of-answers-requiring-attention",
1818 web::get().to(get_all_exercises_and_count_of_answers_requiring_attention),
1819 )
1820 .route(
1821 "/{course_id}/structure",
1822 web::get().to(get_course_structure),
1823 )
1824 .route(
1825 "/{course_id}/language-versions",
1826 web::get().to(get_all_course_language_versions),
1827 )
1828 .route(
1829 "/{course_id}/create-copy",
1830 web::post().to(create_course_copy),
1831 )
1832 .route("/{course_id}/upload", web::post().to(add_media_for_course))
1833 .route(
1834 "/{course_id}/weekday-hour-submission-counts",
1835 web::get().to(get_weekday_hour_submission_counts),
1836 )
1837 .route(
1838 "/{course_id}/submission-counts-by-exercise",
1839 web::get().to(get_submission_counts_by_exercise),
1840 )
1841 .route(
1842 "/{course_id}/course-instances",
1843 web::get().to(get_course_instances),
1844 )
1845 .route("/{course_id}/feedback", web::get().to(get_feedback))
1846 .route(
1847 "/{course_id}/feedback-count",
1848 web::get().to(get_feedback_count),
1849 )
1850 .route(
1851 "/{course_id}/new-course-instance",
1852 web::post().to(new_course_instance),
1853 )
1854 .route("/{course_id}/glossary", web::get().to(glossary))
1855 .route("/{course_id}/glossary", web::post().to(new_glossary_term))
1856 .route(
1857 "/{course_id}/course-users-counts-by-exercise",
1858 web::get().to(get_course_users_counts_by_exercise),
1859 )
1860 .route(
1861 "/{course_id}/new-page-ordering",
1862 web::post().to(post_new_page_ordering),
1863 )
1864 .route(
1865 "/{course_id}/new-chapter-ordering",
1866 web::post().to(post_new_chapter_ordering),
1867 )
1868 .route(
1869 "/{course_id}/references",
1870 web::get().to(get_material_references_by_course_id),
1871 )
1872 .route(
1873 "/{course_id}/references",
1874 web::post().to(insert_material_references),
1875 )
1876 .route(
1877 "/{course_id}/references/{reference_id}",
1878 web::post().to(update_material_reference),
1879 )
1880 .route(
1881 "/{course_id}/references/{reference_id}",
1882 web::delete().to(delete_material_reference_by_id),
1883 )
1884 .route(
1885 "/{course_id}/course-modules",
1886 web::post().to(update_modules),
1887 )
1888 .route(
1889 "/{course_id}/default-peer-review",
1890 web::get().to(get_course_default_peer_review),
1891 )
1892 .route(
1893 "/{course_id}/update-peer-review-queue-reviews-received",
1894 web::post().to(post_update_peer_review_queue_reviews_received),
1895 )
1896 .route(
1897 "/{course_id}/breadcrumb-info",
1898 web::get().to(get_course_breadcrumb_info),
1899 )
1900 .route(
1901 "/{course_id}/progress/{user_id}",
1902 web::get().to(get_user_progress_for_course),
1903 )
1904 .route(
1905 "/{course_id}/user-settings/{user_id}",
1906 web::get().to(get_user_course_settings),
1907 )
1908 .route(
1909 "/{course_id}/export-submissions",
1910 web::get().to(submission_export),
1911 )
1912 .route(
1913 "/{course_id}/export-user-details",
1914 web::get().to(user_details_export),
1915 )
1916 .route(
1917 "/{course_id}/export-exercise-tasks",
1918 web::get().to(exercise_tasks_export),
1919 )
1920 .route(
1921 "/{course_id}/export-course-instances",
1922 web::get().to(course_instances_export),
1923 )
1924 .route(
1925 "/{course_id}/export-course-user-consents",
1926 web::get().to(course_consent_form_answers_export),
1927 )
1928 .route(
1929 "/{course_id}/export-user-exercise-states",
1930 web::get().to(user_exercise_states_export),
1931 )
1932 .route(
1933 "/{course_id}/page-visit-datum-summary",
1934 web::get().to(get_page_visit_datum_summary),
1935 )
1936 .route(
1937 "/{course_id}/page-visit-datum-summary-by-pages",
1938 web::get().to(get_page_visit_datum_summary_by_pages),
1939 )
1940 .route(
1941 "/{course_id}/page-visit-datum-summary-by-device-types",
1942 web::get().to(get_page_visit_datum_summary_by_device_types),
1943 )
1944 .route(
1945 "/{course_id}/page-visit-datum-summary-by-countries",
1946 web::get().to(get_page_visit_datum_summary_by_countries),
1947 )
1948 .route(
1949 "/{course_id}/teacher-reset-course-progress-for-themselves",
1950 web::delete().to(teacher_reset_course_progress_for_themselves),
1951 )
1952 .route("/{course_id}/thresholds", web::get().to(get_all_thresholds))
1953 .route(
1954 "/{course_id}/suspected-cheaters",
1955 web::get().to(get_all_suspected_cheaters),
1956 )
1957 .route(
1958 "/{course_id}/suspected-cheaters/archive/{id}",
1959 web::post().to(teacher_archive_suspected_cheater),
1960 )
1961 .route(
1962 "/{course_id}/suspected-cheaters/approve/{id}",
1963 web::post().to(teacher_approve_suspected_cheater),
1964 )
1965 .route(
1966 "/{course_id}/teacher-reset-course-progress-for-everyone",
1967 web::delete().to(teacher_reset_course_progress_for_everyone),
1968 )
1969 .route(
1970 "/{course_id}/join-course-with-join-code",
1971 web::post().to(add_user_to_course_with_join_code),
1972 )
1973 .route(
1974 "/{course_id}/partners-block",
1975 web::post().to(post_partners_block),
1976 )
1977 .route(
1978 "/{course_id}/partners-block",
1979 web::get().to(get_partners_block),
1980 )
1981 .route(
1982 "/{course_id}/partners-block",
1983 web::delete().to(delete_partners_block),
1984 )
1985 .route(
1986 "/{course_id}/set-join-code",
1987 web::post().to(set_join_code_for_course),
1988 )
1989 .route(
1990 "/{course_id}/reprocess-completions",
1991 web::post().to(post_reprocess_module_completions),
1992 )
1993 .route(
1994 "/join/{join_code}",
1995 web::get().to(get_course_with_join_code),
1996 );
1997}