1use crate::{
2 error::Result,
3 http::Requester,
4 pagination::PageStream,
5 params::wrap_params,
6 resources::{
7 assignment::Assignment,
8 blueprint::{BlueprintSubscription, BlueprintTemplate},
9 collaboration::Collaboration,
10 content_export::{ContentExport, ContentExportParams},
11 content_migration::{ContentMigration, Migrator},
12 discussion_topic::DiscussionTopic,
13 enrollment::Enrollment,
14 external_tool::{ExternalTool, ExternalToolParams},
15 feature::{Feature, FeatureFlag},
16 file::File,
17 grade_change_log::GradeChangeEvent,
18 gradebook_history::{Day, Grader, SubmissionHistory, SubmissionVersion},
19 grading_period::GradingPeriod,
20 grading_standard::GradingStandard,
21 group::Group,
22 lti_resource_link::{CreateLtiResourceLinkParams, LtiResourceLink},
23 module::Module,
24 outcome::{OutcomeGroup, OutcomeLink, UpdateOutcomeGroupParams},
25 page::Page,
26 params::{
27 assignment_params::CreateAssignmentParams, course_params::UpdateCourseParams,
28 quiz_params::CreateQuizParams,
29 },
30 quiz::Quiz,
31 rubric::{Rubric, RubricAssociation, RubricParams},
32 section::Section,
33 tab::Tab,
34 types::WorkflowState,
35 user::User,
36 },
37};
38use chrono::{DateTime, Utc};
39use serde::{Deserialize, Serialize};
40use std::sync::Arc;
41
42#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
43pub struct Course {
44 pub id: u64,
45 pub name: Option<String>,
46 pub course_code: Option<String>,
47 pub workflow_state: Option<WorkflowState>,
48 pub account_id: Option<u64>,
49 pub root_account_id: Option<u64>,
50 pub enrollment_term_id: Option<u64>,
51 pub sis_course_id: Option<String>,
52 pub start_at: Option<DateTime<Utc>>,
53 pub end_at: Option<DateTime<Utc>>,
54 pub grading_standard_id: Option<u64>,
55 pub is_public: Option<bool>,
56 pub license: Option<String>,
57 pub locale: Option<String>,
58 pub time_zone: Option<String>,
59 pub total_students: Option<u64>,
60 pub default_view: Option<String>,
61 pub syllabus_body: Option<String>,
62 pub public_description: Option<String>,
63 pub hide_final_grades: Option<bool>,
64 pub apply_assignment_group_weights: Option<bool>,
65 pub restrict_enrollments_to_course_dates: Option<bool>,
66
67 #[serde(skip)]
68 pub(crate) requester: Option<Arc<Requester>>,
69}
70
71impl Course {
72 pub fn get_assignments(&self) -> PageStream<Assignment> {
77 PageStream::new(
78 Arc::clone(self.req()),
79 &format!("courses/{}/assignments", self.id),
80 vec![],
81 )
82 }
83
84 pub async fn get_assignment(&self, assignment_id: u64) -> Result<Assignment> {
89 self.req()
90 .get(
91 &format!("courses/{}/assignments/{assignment_id}", self.id),
92 &[],
93 )
94 .await
95 }
96
97 pub async fn create_assignment(&self, params: CreateAssignmentParams) -> Result<Assignment> {
102 let form = wrap_params("assignment", ¶ms);
103 self.req()
104 .post(&format!("courses/{}/assignments", self.id), &form)
105 .await
106 }
107
108 pub fn get_sections(&self) -> PageStream<Section> {
113 PageStream::new(
114 Arc::clone(self.req()),
115 &format!("courses/{}/sections", self.id),
116 vec![],
117 )
118 }
119
120 pub async fn get_section(&self, section_id: u64) -> Result<Section> {
125 self.req()
126 .get(&format!("courses/{}/sections/{section_id}", self.id), &[])
127 .await
128 }
129
130 pub fn get_enrollments(&self) -> PageStream<Enrollment> {
135 PageStream::new(
136 Arc::clone(self.req()),
137 &format!("courses/{}/enrollments", self.id),
138 vec![],
139 )
140 }
141
142 pub fn get_users(&self) -> PageStream<User> {
147 PageStream::new(
148 Arc::clone(self.req()),
149 &format!("courses/{}/users", self.id),
150 vec![],
151 )
152 }
153
154 pub async fn update(&self, params: UpdateCourseParams) -> Result<Course> {
159 let form = wrap_params("course", ¶ms);
160 let mut course: Course = self
161 .req()
162 .put(&format!("courses/{}", self.id), &form)
163 .await?;
164 course.requester = self.requester.clone();
165 Ok(course)
166 }
167
168 pub async fn delete(&self) -> Result<Course> {
173 let params = vec![("event".to_string(), "delete".to_string())];
174 let mut course: Course = self
175 .req()
176 .delete(&format!("courses/{}", self.id), ¶ms)
177 .await?;
178 course.requester = self.requester.clone();
179 Ok(course)
180 }
181
182 pub fn get_quizzes(&self) -> PageStream<Quiz> {
187 PageStream::new(
188 Arc::clone(self.req()),
189 &format!("courses/{}/quizzes", self.id),
190 vec![],
191 )
192 }
193
194 pub async fn get_quiz(&self, quiz_id: u64) -> Result<Quiz> {
199 self.req()
200 .get(&format!("courses/{}/quizzes/{quiz_id}", self.id), &[])
201 .await
202 }
203
204 pub async fn create_quiz(&self, params: CreateQuizParams) -> Result<Quiz> {
209 let form = wrap_params("quiz", ¶ms);
210 self.req()
211 .post(&format!("courses/{}/quizzes", self.id), &form)
212 .await
213 }
214
215 pub fn get_modules(&self) -> PageStream<Module> {
220 PageStream::new(
221 Arc::clone(self.req()),
222 &format!("courses/{}/modules", self.id),
223 vec![],
224 )
225 }
226
227 pub async fn get_module(&self, module_id: u64) -> Result<Module> {
232 self.req()
233 .get(&format!("courses/{}/modules/{module_id}", self.id), &[])
234 .await
235 }
236
237 pub fn get_pages(&self) -> PageStream<Page> {
242 PageStream::new(
243 Arc::clone(self.req()),
244 &format!("courses/{}/pages", self.id),
245 vec![],
246 )
247 }
248
249 pub async fn get_page(&self, url_or_id: &str) -> Result<Page> {
254 self.req()
255 .get(&format!("courses/{}/pages/{url_or_id}", self.id), &[])
256 .await
257 }
258
259 pub fn get_discussion_topics(&self) -> PageStream<DiscussionTopic> {
264 PageStream::new(
265 Arc::clone(self.req()),
266 &format!("courses/{}/discussion_topics", self.id),
267 vec![],
268 )
269 }
270
271 pub async fn get_discussion_topic(&self, topic_id: u64) -> Result<DiscussionTopic> {
276 self.req()
277 .get(
278 &format!("courses/{}/discussion_topics/{topic_id}", self.id),
279 &[],
280 )
281 .await
282 }
283
284 pub fn get_files(&self) -> PageStream<File> {
289 PageStream::new(
290 Arc::clone(self.req()),
291 &format!("courses/{}/files", self.id),
292 vec![],
293 )
294 }
295
296 pub fn get_tabs(&self) -> PageStream<Tab> {
301 PageStream::new(
302 Arc::clone(self.req()),
303 &format!("courses/{}/tabs", self.id),
304 vec![],
305 )
306 }
307
308 pub fn get_collaborations(&self) -> PageStream<Collaboration> {
313 let course_id = self.id;
314 PageStream::new_with_injector(
315 Arc::clone(self.req()),
316 &format!("courses/{course_id}/collaborations"),
317 vec![],
318 {
319 let req = Arc::clone(self.req());
320 move |mut c: Collaboration, _| {
321 c.requester = Some(Arc::clone(&req));
322 c
323 }
324 },
325 )
326 }
327
328 pub fn get_groups(&self) -> PageStream<Group> {
333 let course_id = self.id;
334 PageStream::new_with_injector(
335 Arc::clone(self.req()),
336 &format!("courses/{course_id}/groups"),
337 vec![],
338 |mut g: Group, req| {
339 g.requester = Some(Arc::clone(&req));
340 g
341 },
342 )
343 }
344
345 pub async fn upload_file(
353 &self,
354 request: crate::upload::UploadRequest,
355 data: Vec<u8>,
356 ) -> crate::error::Result<crate::resources::file::File> {
357 crate::upload::initiate_and_upload(
358 self.req(),
359 &format!("courses/{}/files", self.id),
360 request,
361 data,
362 )
363 .await
364 }
365
366 pub async fn get_external_tool(&self, tool_id: u64) -> Result<ExternalTool> {
375 let mut tool: ExternalTool = self
376 .req()
377 .get(
378 &format!("courses/{}/external_tools/{tool_id}", self.id),
379 &[],
380 )
381 .await?;
382 tool.requester = self.requester.clone();
383 Ok(tool)
384 }
385
386 pub fn get_external_tools(&self) -> PageStream<ExternalTool> {
391 let course_id = self.id;
392 PageStream::new_with_injector(
393 Arc::clone(self.req()),
394 &format!("courses/{course_id}/external_tools"),
395 vec![],
396 |mut t: ExternalTool, req| {
397 t.requester = Some(Arc::clone(&req));
398 t
399 },
400 )
401 }
402
403 pub async fn create_external_tool(&self, params: ExternalToolParams) -> Result<ExternalTool> {
408 let form = wrap_params("external_tool", ¶ms);
409 let mut tool: ExternalTool = self
410 .req()
411 .post(&format!("courses/{}/external_tools", self.id), &form)
412 .await?;
413 tool.requester = self.requester.clone();
414 Ok(tool)
415 }
416
417 pub async fn get_rubric(&self, rubric_id: u64) -> Result<Rubric> {
426 let mut rubric: Rubric = self
427 .req()
428 .get(&format!("courses/{}/rubrics/{rubric_id}", self.id), &[])
429 .await?;
430 rubric.requester = self.requester.clone();
431 Ok(rubric)
432 }
433
434 pub fn get_rubrics(&self) -> PageStream<Rubric> {
439 let course_id = self.id;
440 PageStream::new_with_injector(
441 Arc::clone(self.req()),
442 &format!("courses/{course_id}/rubrics"),
443 vec![],
444 |mut r: Rubric, req| {
445 r.requester = Some(Arc::clone(&req));
446 r
447 },
448 )
449 }
450
451 pub async fn create_rubric(&self, params: RubricParams) -> Result<Rubric> {
456 let form = wrap_params("rubric", ¶ms);
457 let mut rubric: Rubric = self
458 .req()
459 .post(&format!("courses/{}/rubrics", self.id), &form)
460 .await?;
461 rubric.requester = self.requester.clone();
462 Ok(rubric)
463 }
464
465 pub async fn get_rubric_association(&self, association_id: u64) -> Result<RubricAssociation> {
470 let mut assoc: RubricAssociation = self
471 .req()
472 .get(
473 &format!("courses/{}/rubric_associations/{association_id}", self.id),
474 &[],
475 )
476 .await?;
477 assoc.requester = self.requester.clone();
478 Ok(assoc)
479 }
480
481 pub fn get_rubric_associations(&self) -> PageStream<RubricAssociation> {
486 let course_id = self.id;
487 PageStream::new_with_injector(
488 Arc::clone(self.req()),
489 &format!("courses/{course_id}/rubric_associations"),
490 vec![],
491 |mut a: RubricAssociation, req| {
492 a.requester = Some(Arc::clone(&req));
493 a
494 },
495 )
496 }
497
498 pub async fn get_blueprint(&self, template_id: &str) -> Result<BlueprintTemplate> {
509 let mut tmpl: BlueprintTemplate = self
510 .req()
511 .get(
512 &format!("courses/{}/blueprint_templates/{template_id}", self.id),
513 &[],
514 )
515 .await?;
516 tmpl.requester = self.requester.clone();
517 Ok(tmpl)
518 }
519
520 pub fn get_blueprint_subscriptions(&self) -> PageStream<BlueprintSubscription> {
525 let course_id = self.id;
526 PageStream::new_with_injector(
527 Arc::clone(self.req()),
528 &format!("courses/{course_id}/blueprint_subscriptions"),
529 vec![],
530 |mut s: BlueprintSubscription, req| {
531 s.requester = Some(Arc::clone(&req));
532 s
533 },
534 )
535 }
536
537 pub async fn get_content_migration(&self, migration_id: u64) -> Result<ContentMigration> {
546 let mut migration: ContentMigration = self
547 .req()
548 .get(
549 &format!("courses/{}/content_migrations/{migration_id}", self.id),
550 &[],
551 )
552 .await?;
553 migration.requester = self.requester.clone();
554 Ok(migration)
555 }
556
557 pub fn get_content_migrations(&self) -> PageStream<ContentMigration> {
562 let course_id = self.id;
563 PageStream::new_with_injector(
564 Arc::clone(self.req()),
565 &format!("courses/{course_id}/content_migrations"),
566 vec![],
567 |mut m: ContentMigration, req| {
568 m.requester = Some(Arc::clone(&req));
569 m
570 },
571 )
572 }
573
574 pub async fn create_content_migration(
579 &self,
580 migration_type: &str,
581 params: &[(String, String)],
582 ) -> Result<ContentMigration> {
583 let mut form = vec![("migration_type".to_string(), migration_type.to_string())];
584 form.extend_from_slice(params);
585 let mut migration: ContentMigration = self
586 .req()
587 .post(&format!("courses/{}/content_migrations", self.id), &form)
588 .await?;
589 migration.requester = self.requester.clone();
590 Ok(migration)
591 }
592
593 pub fn get_migrators(&self) -> PageStream<Migrator> {
598 PageStream::new(
599 Arc::clone(self.req()),
600 &format!("courses/{}/content_migrations/migrators", self.id),
601 vec![],
602 )
603 }
604
605 pub fn get_outcome_group_links(&self) -> PageStream<OutcomeLink> {
614 PageStream::new(
615 Arc::clone(self.req()),
616 &format!("courses/{}/outcome_group_links", self.id),
617 vec![],
618 )
619 }
620
621 pub async fn get_outcome_group(&self, group_id: u64) -> Result<OutcomeGroup> {
626 let mut group: OutcomeGroup = self
627 .req()
628 .get(
629 &format!("courses/{}/outcome_groups/{group_id}", self.id),
630 &[],
631 )
632 .await?;
633 group.requester = self.requester.clone();
634 Ok(group)
635 }
636
637 pub async fn create_outcome_group(
642 &self,
643 params: UpdateOutcomeGroupParams,
644 ) -> Result<OutcomeGroup> {
645 let form = wrap_params("outcome_group", ¶ms);
646 let mut group: OutcomeGroup = self
647 .req()
648 .post(&format!("courses/{}/outcome_groups", self.id), &form)
649 .await?;
650 group.requester = self.requester.clone();
651 Ok(group)
652 }
653
654 pub fn get_gradebook_history_dates(&self) -> PageStream<Day> {
663 PageStream::new(
664 Arc::clone(self.req()),
665 &format!("courses/{}/gradebook_history/days", self.id),
666 vec![],
667 )
668 }
669
670 pub fn get_gradebook_history_details(&self, date: &str) -> PageStream<Grader> {
677 PageStream::new(
678 Arc::clone(self.req()),
679 &format!("courses/{}/gradebook_history/{date}", self.id),
680 vec![],
681 )
682 }
683
684 pub fn get_submission_history(
689 &self,
690 date: &str,
691 grader_id: u64,
692 assignment_id: u64,
693 ) -> PageStream<SubmissionHistory> {
694 PageStream::new(
695 Arc::clone(self.req()),
696 &format!(
697 "courses/{}/gradebook_history/{date}/graders/{grader_id}/assignments/{assignment_id}/submissions",
698 self.id
699 ),
700 vec![],
701 )
702 }
703
704 pub fn get_uncollated_submissions(&self) -> PageStream<SubmissionVersion> {
709 PageStream::new(
710 Arc::clone(self.req()),
711 &format!("courses/{}/gradebook_history/feed", self.id),
712 vec![],
713 )
714 }
715
716 #[cfg(feature = "new-quizzes")]
725 pub async fn get_new_quiz(
726 &self,
727 assignment_id: &str,
728 ) -> Result<crate::resources::new_quiz::NewQuiz> {
729 let mut quiz: crate::resources::new_quiz::NewQuiz = self
730 .req()
731 .nq_get(&format!("courses/{}/quizzes/{assignment_id}", self.id), &[])
732 .await?;
733 quiz.requester = self.requester.clone();
734 quiz.course_id = Some(self.id);
735 Ok(quiz)
736 }
737
738 #[cfg(feature = "new-quizzes")]
743 pub fn get_new_quizzes(&self) -> PageStream<crate::resources::new_quiz::NewQuiz> {
744 let course_id = self.id;
745 PageStream::new_with_injector_nq(
746 Arc::clone(self.req()),
747 &format!("courses/{course_id}/quizzes"),
748 vec![],
749 move |mut q: crate::resources::new_quiz::NewQuiz, req| {
750 q.requester = Some(Arc::clone(&req));
751 q.course_id = Some(course_id);
752 q
753 },
754 )
755 }
756
757 #[cfg(feature = "new-quizzes")]
762 pub async fn create_new_quiz(
763 &self,
764 params: crate::resources::new_quiz::NewQuizParams,
765 ) -> Result<crate::resources::new_quiz::NewQuiz> {
766 let body = serde_json::to_value(¶ms).unwrap_or_default();
767 let mut quiz: crate::resources::new_quiz::NewQuiz = self
768 .req()
769 .nq_post(&format!("courses/{}/quizzes", self.id), &body)
770 .await?;
771 quiz.requester = self.requester.clone();
772 quiz.course_id = Some(self.id);
773 Ok(quiz)
774 }
775
776 pub fn get_grading_periods(&self) -> PageStream<GradingPeriod> {
785 let course_id = self.id;
786 PageStream::new_with_injector(
787 Arc::clone(self.req()),
788 &format!("courses/{course_id}/grading_periods"),
789 vec![],
790 move |mut gp: GradingPeriod, req| {
791 gp.requester = Some(Arc::clone(&req));
792 gp.course_id = Some(course_id);
793 gp
794 },
795 )
796 }
797
798 pub fn get_grading_standards(&self) -> PageStream<GradingStandard> {
807 PageStream::new(
808 Arc::clone(self.req()),
809 &format!("courses/{}/grading_standards", self.id),
810 vec![],
811 )
812 }
813
814 pub async fn create_grading_standard(
819 &self,
820 params: crate::resources::grading_standard::GradingStandardParams,
821 ) -> Result<GradingStandard> {
822 let form = wrap_params("grading_scheme_entry", ¶ms.grading_scheme_entry)
823 .into_iter()
824 .chain([("title".into(), params.title)])
825 .collect::<Vec<_>>();
826 self.req()
827 .post(&format!("courses/{}/grading_standards", self.id), &form)
828 .await
829 }
830
831 pub async fn get_content_export(&self, export_id: u64) -> Result<ContentExport> {
840 self.req()
841 .get(
842 &format!("courses/{}/content_exports/{export_id}", self.id),
843 &[],
844 )
845 .await
846 }
847
848 pub fn get_content_exports(&self) -> PageStream<ContentExport> {
853 PageStream::new(
854 Arc::clone(self.req()),
855 &format!("courses/{}/content_exports", self.id),
856 vec![],
857 )
858 }
859
860 pub async fn create_content_export(
865 &self,
866 params: ContentExportParams,
867 ) -> Result<ContentExport> {
868 let form = vec![
869 ("export_type".into(), params.export_type),
870 (
871 "skip_notifications".into(),
872 params.skip_notifications.unwrap_or(false).to_string(),
873 ),
874 ];
875 self.req()
876 .post(&format!("courses/{}/content_exports", self.id), &form)
877 .await
878 }
879
880 pub fn get_grade_change_events(&self) -> PageStream<GradeChangeEvent> {
892 PageStream::new(
893 Arc::clone(self.req()),
894 &format!("audit/grade_change/courses/{}", self.id),
895 vec![],
896 )
897 }
898
899 pub fn get_features(&self) -> PageStream<Feature> {
908 PageStream::new(
909 Arc::clone(self.req()),
910 &format!("courses/{}/features", self.id),
911 vec![],
912 )
913 }
914
915 pub async fn get_feature_flag(&self, feature: &str) -> Result<FeatureFlag> {
920 self.req()
921 .get(
922 &format!("courses/{}/features/flags/{feature}", self.id),
923 &[],
924 )
925 .await
926 }
927
928 pub async fn get_enabled_features(&self) -> Result<Vec<String>> {
933 self.req()
934 .get(&format!("courses/{}/features/enabled", self.id), &[])
935 .await
936 }
937
938 pub fn get_lti_resource_links(&self) -> PageStream<LtiResourceLink> {
947 PageStream::new(
948 Arc::clone(self.req()),
949 &format!("courses/{}/lti_resource_links", self.id),
950 vec![],
951 )
952 }
953
954 pub async fn get_lti_resource_link(&self, link_id: u64) -> Result<LtiResourceLink> {
959 self.req()
960 .get(
961 &format!("courses/{}/lti_resource_links/{link_id}", self.id),
962 &[],
963 )
964 .await
965 }
966
967 pub async fn create_lti_resource_link(
972 &self,
973 params: CreateLtiResourceLinkParams,
974 ) -> Result<LtiResourceLink> {
975 let form = crate::params::flatten_params(&serde_json::to_value(¶ms).unwrap());
976 self.req()
977 .post(&format!("courses/{}/lti_resource_links", self.id), &form)
978 .await
979 }
980}