Skip to main content

canvas_lms_api/resources/
course.rs

1use crate::{
2    error::Result,
3    http::Requester,
4    pagination::PageStream,
5    params::wrap_params,
6    resources::{
7        assignment::{Assignment, AssignmentGroup},
8        blueprint::{BlueprintSubscription, BlueprintTemplate},
9        collaboration::Collaboration,
10        content_export::{ContentExport, ContentExportParams},
11        content_migration::{ContentMigration, Migrator},
12        custom_gradebook_column::{CustomGradebookColumn, CustomGradebookColumnParams},
13        discussion_topic::DiscussionTopic,
14        enrollment::Enrollment,
15        external_tool::{ExternalTool, ExternalToolParams},
16        feature::{Feature, FeatureFlag},
17        file::File,
18        grade_change_log::GradeChangeEvent,
19        gradebook_history::{Day, Grader, SubmissionHistory, SubmissionVersion},
20        grading_period::GradingPeriod,
21        grading_standard::GradingStandard,
22        group::{Group, GroupCategory},
23        lti_resource_link::{CreateLtiResourceLinkParams, LtiResourceLink},
24        module::Module,
25        outcome::{OutcomeGroup, OutcomeLink, UpdateOutcomeGroupParams},
26        page::Page,
27        params::{
28            assignment_params::CreateAssignmentParams, course_params::UpdateCourseParams,
29            quiz_params::CreateQuizParams,
30        },
31        quiz::Quiz,
32        rubric::{Rubric, RubricAssociation, RubricParams},
33        section::Section,
34        tab::Tab,
35        types::WorkflowState,
36        user::User,
37    },
38};
39use chrono::{DateTime, Utc};
40use serde::{Deserialize, Serialize};
41use std::sync::Arc;
42
43#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
44pub struct Course {
45    pub id: u64,
46    pub name: Option<String>,
47    pub course_code: Option<String>,
48    pub workflow_state: Option<WorkflowState>,
49    pub account_id: Option<u64>,
50    pub root_account_id: Option<u64>,
51    pub enrollment_term_id: Option<u64>,
52    pub sis_course_id: Option<String>,
53    pub start_at: Option<DateTime<Utc>>,
54    pub end_at: Option<DateTime<Utc>>,
55    pub grading_standard_id: Option<u64>,
56    pub is_public: Option<bool>,
57    pub license: Option<String>,
58    pub locale: Option<String>,
59    pub time_zone: Option<String>,
60    pub total_students: Option<u64>,
61    pub default_view: Option<String>,
62    pub syllabus_body: Option<String>,
63    pub public_description: Option<String>,
64    pub hide_final_grades: Option<bool>,
65    pub apply_assignment_group_weights: Option<bool>,
66    pub restrict_enrollments_to_course_dates: Option<bool>,
67
68    #[serde(skip)]
69    pub(crate) requester: Option<Arc<Requester>>,
70}
71
72impl Course {
73    /// Stream all assignments in this course.
74    ///
75    /// # Canvas API
76    /// `GET /api/v1/courses/:course_id/assignments`
77    pub fn get_assignments(&self) -> PageStream<Assignment> {
78        let course_id = self.id;
79        PageStream::new_with_injector(
80            Arc::clone(self.req()),
81            &format!("courses/{}/assignments", self.id),
82            vec![],
83            move |mut a: Assignment, req| {
84                a.requester = Some(Arc::clone(&req));
85                a.course_id = Some(course_id);
86                a
87            },
88        )
89    }
90
91    /// Fetch a single assignment.
92    ///
93    /// # Canvas API
94    /// `GET /api/v1/courses/:course_id/assignments/:id`
95    pub async fn get_assignment(&self, assignment_id: u64) -> Result<Assignment> {
96        let mut a: Assignment = self
97            .req()
98            .get(
99                &format!("courses/{}/assignments/{assignment_id}", self.id),
100                &[],
101            )
102            .await?;
103        a.requester = Some(Arc::clone(self.req()));
104        a.course_id = Some(self.id);
105        Ok(a)
106    }
107
108    /// Create a new assignment in this course.
109    ///
110    /// # Canvas API
111    /// `POST /api/v1/courses/:id/assignments`
112    pub async fn create_assignment(&self, params: CreateAssignmentParams) -> Result<Assignment> {
113        let form = wrap_params("assignment", &params);
114        let mut a: Assignment = self
115            .req()
116            .post(&format!("courses/{}/assignments", self.id), &form)
117            .await?;
118        a.requester = Some(Arc::clone(self.req()));
119        a.course_id = Some(self.id);
120        Ok(a)
121    }
122
123    /// Stream all assignment groups in this course.
124    ///
125    /// # Canvas API
126    /// `GET /api/v1/courses/:id/assignment_groups`
127    pub fn get_assignment_groups(&self) -> PageStream<AssignmentGroup> {
128        let course_id = self.id;
129        PageStream::new_with_injector(
130            Arc::clone(self.req()),
131            &format!("courses/{}/assignment_groups", self.id),
132            vec![],
133            move |mut g: AssignmentGroup, req| {
134                g.requester = Some(Arc::clone(&req));
135                g.course_id = Some(course_id);
136                g
137            },
138        )
139    }
140
141    /// Create a new assignment group in this course.
142    ///
143    /// # Canvas API
144    /// `POST /api/v1/courses/:id/assignment_groups`
145    pub async fn create_assignment_group(
146        &self,
147        params: crate::resources::assignment::AssignmentGroupParams,
148    ) -> Result<AssignmentGroup> {
149        let form = wrap_params("assignment_group", &params);
150        let mut g: AssignmentGroup = self
151            .req()
152            .post(&format!("courses/{}/assignment_groups", self.id), &form)
153            .await?;
154        g.requester = Some(Arc::clone(self.req()));
155        g.course_id = Some(self.id);
156        Ok(g)
157    }
158
159    /// Stream all sections in this course.
160    ///
161    /// # Canvas API
162    /// `GET /api/v1/courses/:course_id/sections`
163    pub fn get_sections(&self) -> PageStream<Section> {
164        PageStream::new_with_injector(
165            Arc::clone(self.req()),
166            &format!("courses/{}/sections", self.id),
167            vec![],
168            |mut s: Section, req| {
169                s.requester = Some(Arc::clone(&req));
170                s
171            },
172        )
173    }
174
175    /// Fetch a single section by ID.
176    ///
177    /// # Canvas API
178    /// `GET /api/v1/courses/:id/sections/:section_id`
179    pub async fn get_section(&self, section_id: u64) -> Result<Section> {
180        let mut s: Section = self
181            .req()
182            .get(&format!("courses/{}/sections/{section_id}", self.id), &[])
183            .await?;
184        s.requester = self.requester.clone();
185        Ok(s)
186    }
187
188    /// Stream all enrollments in this course.
189    ///
190    /// # Canvas API
191    /// `GET /api/v1/courses/:course_id/enrollments`
192    pub fn get_enrollments(&self) -> PageStream<Enrollment> {
193        PageStream::new_with_injector(
194            Arc::clone(self.req()),
195            &format!("courses/{}/enrollments", self.id),
196            vec![],
197            |mut e: Enrollment, req| {
198                e.requester = Some(Arc::clone(&req));
199                e
200            },
201        )
202    }
203
204    /// Stream all users in this course.
205    ///
206    /// # Canvas API
207    /// `GET /api/v1/courses/:course_id/users`
208    pub fn get_users(&self) -> PageStream<User> {
209        PageStream::new(
210            Arc::clone(self.req()),
211            &format!("courses/{}/users", self.id),
212            vec![],
213        )
214    }
215
216    /// Update this course.
217    ///
218    /// # Canvas API
219    /// `PUT /api/v1/courses/:id`
220    pub async fn update(&self, params: UpdateCourseParams) -> Result<Course> {
221        let form = wrap_params("course", &params);
222        let mut course: Course = self
223            .req()
224            .put(&format!("courses/{}", self.id), &form)
225            .await?;
226        course.requester = self.requester.clone();
227        Ok(course)
228    }
229
230    /// Delete this course. Canvas returns the deleted course object.
231    ///
232    /// # Canvas API
233    /// `DELETE /api/v1/courses/:id`
234    pub async fn delete(&self) -> Result<Course> {
235        let params = vec![("event".to_string(), "delete".to_string())];
236        let mut course: Course = self
237            .req()
238            .delete(&format!("courses/{}", self.id), &params)
239            .await?;
240        course.requester = self.requester.clone();
241        Ok(course)
242    }
243
244    /// Stream all quizzes in this course.
245    ///
246    /// # Canvas API
247    /// `GET /api/v1/courses/:id/quizzes`
248    pub fn get_quizzes(&self) -> PageStream<Quiz> {
249        let course_id = self.id;
250        PageStream::new_with_injector(
251            Arc::clone(self.req()),
252            &format!("courses/{}/quizzes", self.id),
253            vec![],
254            move |mut q: Quiz, req| {
255                q.requester = Some(Arc::clone(&req));
256                q.course_id = Some(course_id);
257                q
258            },
259        )
260    }
261
262    /// Fetch a single quiz.
263    ///
264    /// # Canvas API
265    /// `GET /api/v1/courses/:id/quizzes/:quiz_id`
266    pub async fn get_quiz(&self, quiz_id: u64) -> Result<Quiz> {
267        let mut q: Quiz = self
268            .req()
269            .get(&format!("courses/{}/quizzes/{quiz_id}", self.id), &[])
270            .await?;
271        q.requester = Some(Arc::clone(self.req()));
272        q.course_id = Some(self.id);
273        Ok(q)
274    }
275
276    /// Create a new quiz in this course.
277    ///
278    /// # Canvas API
279    /// `POST /api/v1/courses/:id/quizzes`
280    pub async fn create_quiz(&self, params: CreateQuizParams) -> Result<Quiz> {
281        let form = wrap_params("quiz", &params);
282        let mut q: Quiz = self
283            .req()
284            .post(&format!("courses/{}/quizzes", self.id), &form)
285            .await?;
286        q.requester = Some(Arc::clone(self.req()));
287        q.course_id = Some(self.id);
288        Ok(q)
289    }
290
291    /// Stream all modules in this course.
292    ///
293    /// # Canvas API
294    /// `GET /api/v1/courses/:id/modules`
295    pub fn get_modules(&self) -> PageStream<Module> {
296        let course_id = self.id;
297        PageStream::new_with_injector(
298            Arc::clone(self.req()),
299            &format!("courses/{}/modules", self.id),
300            vec![],
301            move |mut m: Module, req| {
302                m.requester = Some(Arc::clone(&req));
303                m.course_id = Some(course_id);
304                m
305            },
306        )
307    }
308
309    /// Fetch a single module.
310    ///
311    /// # Canvas API
312    /// `GET /api/v1/courses/:id/modules/:module_id`
313    pub async fn get_module(&self, module_id: u64) -> Result<Module> {
314        let mut m: Module = self
315            .req()
316            .get(&format!("courses/{}/modules/{module_id}", self.id), &[])
317            .await?;
318        m.requester = Some(Arc::clone(self.req()));
319        m.course_id = Some(self.id);
320        Ok(m)
321    }
322
323    /// Create a new module in this course.
324    ///
325    /// # Canvas API
326    /// `POST /api/v1/courses/:id/modules`
327    pub async fn create_module(
328        &self,
329        params: crate::resources::module::CreateModuleParams,
330    ) -> Result<Module> {
331        let form = wrap_params("module", &params);
332        let mut m: Module = self
333            .req()
334            .post(&format!("courses/{}/modules", self.id), &form)
335            .await?;
336        m.requester = Some(Arc::clone(self.req()));
337        m.course_id = Some(self.id);
338        Ok(m)
339    }
340
341    /// Stream all pages in this course.
342    ///
343    /// # Canvas API
344    /// `GET /api/v1/courses/:id/pages`
345    pub fn get_pages(&self) -> PageStream<Page> {
346        let course_id = self.id;
347        PageStream::new_with_injector(
348            Arc::clone(self.req()),
349            &format!("courses/{}/pages", self.id),
350            vec![],
351            move |mut p: Page, req| {
352                p.requester = Some(Arc::clone(&req));
353                p.course_id = Some(course_id);
354                p
355            },
356        )
357    }
358
359    /// Fetch a single page by URL slug or ID.
360    ///
361    /// # Canvas API
362    /// `GET /api/v1/courses/:id/pages/:url_or_id`
363    pub async fn get_page(&self, url_or_id: &str) -> Result<Page> {
364        let mut page: Page = self
365            .req()
366            .get(&format!("courses/{}/pages/{url_or_id}", self.id), &[])
367            .await?;
368        page.requester = self.requester.clone();
369        page.course_id = Some(self.id);
370        Ok(page)
371    }
372
373    /// Stream all discussion topics in this course.
374    ///
375    /// # Canvas API
376    /// `GET /api/v1/courses/:id/discussion_topics`
377    pub fn get_discussion_topics(&self) -> PageStream<DiscussionTopic> {
378        let course_id = self.id;
379        PageStream::new_with_injector(
380            Arc::clone(self.req()),
381            &format!("courses/{}/discussion_topics", self.id),
382            vec![],
383            move |mut t: DiscussionTopic, req| {
384                t.requester = Some(Arc::clone(&req));
385                t.course_id_ctx = Some(course_id);
386                t
387            },
388        )
389    }
390
391    /// Fetch a single discussion topic.
392    ///
393    /// # Canvas API
394    /// `GET /api/v1/courses/:id/discussion_topics/:topic_id`
395    pub async fn get_discussion_topic(&self, topic_id: u64) -> Result<DiscussionTopic> {
396        let mut t: DiscussionTopic = self
397            .req()
398            .get(
399                &format!("courses/{}/discussion_topics/{topic_id}", self.id),
400                &[],
401            )
402            .await?;
403        t.requester = Some(Arc::clone(self.req()));
404        t.course_id_ctx = Some(self.id);
405        Ok(t)
406    }
407
408    /// Create a new discussion topic in this course.
409    ///
410    /// # Canvas API
411    /// `POST /api/v1/courses/:id/discussion_topics`
412    pub async fn create_discussion_topic(
413        &self,
414        params: crate::resources::discussion_topic::UpdateDiscussionParams,
415    ) -> Result<DiscussionTopic> {
416        let form = wrap_params("discussion_topic", &params);
417        let mut t: DiscussionTopic = self
418            .req()
419            .post(&format!("courses/{}/discussion_topics", self.id), &form)
420            .await?;
421        t.requester = Some(Arc::clone(self.req()));
422        t.course_id_ctx = Some(self.id);
423        Ok(t)
424    }
425
426    /// Stream all files in this course.
427    ///
428    /// # Canvas API
429    /// `GET /api/v1/courses/:id/files`
430    pub fn get_files(&self) -> PageStream<File> {
431        PageStream::new_with_injector(
432            Arc::clone(self.req()),
433            &format!("courses/{}/files", self.id),
434            vec![],
435            |mut f: File, req| {
436                f.requester = Some(Arc::clone(&req));
437                f
438            },
439        )
440    }
441
442    /// Stream all tabs in this course.
443    ///
444    /// # Canvas API
445    /// `GET /api/v1/courses/:id/tabs`
446    pub fn get_tabs(&self) -> PageStream<Tab> {
447        let course_id = self.id;
448        PageStream::new_with_injector(
449            Arc::clone(self.req()),
450            &format!("courses/{}/tabs", self.id),
451            vec![],
452            move |mut t: Tab, req| {
453                t.requester = Some(Arc::clone(&req));
454                t.course_id = Some(course_id);
455                t
456            },
457        )
458    }
459
460    /// Stream all collaborations in this course.
461    ///
462    /// # Canvas API
463    /// `GET /api/v1/courses/:id/collaborations`
464    pub fn get_collaborations(&self) -> PageStream<Collaboration> {
465        let course_id = self.id;
466        PageStream::new_with_injector(
467            Arc::clone(self.req()),
468            &format!("courses/{course_id}/collaborations"),
469            vec![],
470            {
471                let req = Arc::clone(self.req());
472                move |mut c: Collaboration, _| {
473                    c.requester = Some(Arc::clone(&req));
474                    c
475                }
476            },
477        )
478    }
479
480    /// Stream all groups in this course.
481    ///
482    /// # Canvas API
483    /// `GET /api/v1/courses/:id/groups`
484    pub fn get_groups(&self) -> PageStream<Group> {
485        let course_id = self.id;
486        PageStream::new_with_injector(
487            Arc::clone(self.req()),
488            &format!("courses/{course_id}/groups"),
489            vec![],
490            |mut g: Group, req| {
491                g.requester = Some(Arc::clone(&req));
492                g
493            },
494        )
495    }
496
497    /// Stream all group categories in this course.
498    ///
499    /// # Canvas API
500    /// `GET /api/v1/courses/:id/group_categories`
501    pub fn get_group_categories(&self) -> PageStream<GroupCategory> {
502        let course_id = self.id;
503        PageStream::new_with_injector(
504            Arc::clone(self.req()),
505            &format!("courses/{course_id}/group_categories"),
506            vec![],
507            |mut gc: GroupCategory, req| {
508                gc.requester = Some(Arc::clone(&req));
509                gc
510            },
511        )
512    }
513
514    /// Create a group category in this course.
515    ///
516    /// # Canvas API
517    /// `POST /api/v1/courses/:id/group_categories`
518    pub async fn create_group_category(
519        &self,
520        params: crate::resources::group::GroupCategoryParams,
521    ) -> Result<GroupCategory> {
522        let form = wrap_params("group_category", &params);
523        let mut gc: GroupCategory = self
524            .req()
525            .post(&format!("courses/{}/group_categories", self.id), &form)
526            .await?;
527        gc.requester = Some(Arc::clone(self.req()));
528        Ok(gc)
529    }
530
531    /// Upload a file to this course.
532    ///
533    /// Canvas uses a two-step upload: first POSTing metadata to obtain an upload URL,
534    /// then POSTing the file as multipart form data to that URL.
535    ///
536    /// # Canvas API
537    /// `POST /api/v1/courses/:id/files`
538    pub async fn upload_file(
539        &self,
540        request: crate::upload::UploadRequest,
541        data: Vec<u8>,
542    ) -> crate::error::Result<crate::resources::file::File> {
543        crate::upload::initiate_and_upload(
544            self.req(),
545            &format!("courses/{}/files", self.id),
546            request,
547            data,
548        )
549        .await
550    }
551
552    // -------------------------------------------------------------------------
553    // External Tools
554    // -------------------------------------------------------------------------
555
556    /// Fetch a single external tool by ID.
557    ///
558    /// # Canvas API
559    /// `GET /api/v1/courses/:course_id/external_tools/:id`
560    pub async fn get_external_tool(&self, tool_id: u64) -> Result<ExternalTool> {
561        let mut tool: ExternalTool = self
562            .req()
563            .get(
564                &format!("courses/{}/external_tools/{tool_id}", self.id),
565                &[],
566            )
567            .await?;
568        tool.requester = self.requester.clone();
569        Ok(tool)
570    }
571
572    /// Stream all external tools for this course.
573    ///
574    /// # Canvas API
575    /// `GET /api/v1/courses/:course_id/external_tools`
576    pub fn get_external_tools(&self) -> PageStream<ExternalTool> {
577        let course_id = self.id;
578        PageStream::new_with_injector(
579            Arc::clone(self.req()),
580            &format!("courses/{course_id}/external_tools"),
581            vec![],
582            |mut t: ExternalTool, req| {
583                t.requester = Some(Arc::clone(&req));
584                t
585            },
586        )
587    }
588
589    /// Create an external tool on this course.
590    ///
591    /// # Canvas API
592    /// `POST /api/v1/courses/:course_id/external_tools`
593    pub async fn create_external_tool(&self, params: ExternalToolParams) -> Result<ExternalTool> {
594        let form = wrap_params("external_tool", &params);
595        let mut tool: ExternalTool = self
596            .req()
597            .post(&format!("courses/{}/external_tools", self.id), &form)
598            .await?;
599        tool.requester = self.requester.clone();
600        Ok(tool)
601    }
602
603    // -------------------------------------------------------------------------
604    // Rubrics
605    // -------------------------------------------------------------------------
606
607    /// Fetch a single rubric by ID.
608    ///
609    /// # Canvas API
610    /// `GET /api/v1/courses/:course_id/rubrics/:id`
611    pub async fn get_rubric(&self, rubric_id: u64) -> Result<Rubric> {
612        let mut rubric: Rubric = self
613            .req()
614            .get(&format!("courses/{}/rubrics/{rubric_id}", self.id), &[])
615            .await?;
616        rubric.requester = self.requester.clone();
617        Ok(rubric)
618    }
619
620    /// Stream all rubrics for this course.
621    ///
622    /// # Canvas API
623    /// `GET /api/v1/courses/:course_id/rubrics`
624    pub fn get_rubrics(&self) -> PageStream<Rubric> {
625        let course_id = self.id;
626        PageStream::new_with_injector(
627            Arc::clone(self.req()),
628            &format!("courses/{course_id}/rubrics"),
629            vec![],
630            |mut r: Rubric, req| {
631                r.requester = Some(Arc::clone(&req));
632                r
633            },
634        )
635    }
636
637    /// Create a rubric in this course.
638    ///
639    /// # Canvas API
640    /// `POST /api/v1/courses/:course_id/rubrics`
641    pub async fn create_rubric(&self, params: RubricParams) -> Result<Rubric> {
642        let form = wrap_params("rubric", &params);
643        let mut rubric: Rubric = self
644            .req()
645            .post(&format!("courses/{}/rubrics", self.id), &form)
646            .await?;
647        rubric.requester = self.requester.clone();
648        Ok(rubric)
649    }
650
651    /// Fetch a single rubric association by ID.
652    ///
653    /// # Canvas API
654    /// `GET /api/v1/courses/:course_id/rubric_associations/:id`
655    pub async fn get_rubric_association(&self, association_id: u64) -> Result<RubricAssociation> {
656        let mut assoc: RubricAssociation = self
657            .req()
658            .get(
659                &format!("courses/{}/rubric_associations/{association_id}", self.id),
660                &[],
661            )
662            .await?;
663        assoc.requester = self.requester.clone();
664        Ok(assoc)
665    }
666
667    /// Stream all rubric associations for this course.
668    ///
669    /// # Canvas API
670    /// `GET /api/v1/courses/:course_id/rubric_associations`
671    pub fn get_rubric_associations(&self) -> PageStream<RubricAssociation> {
672        let course_id = self.id;
673        PageStream::new_with_injector(
674            Arc::clone(self.req()),
675            &format!("courses/{course_id}/rubric_associations"),
676            vec![],
677            |mut a: RubricAssociation, req| {
678                a.requester = Some(Arc::clone(&req));
679                a
680            },
681        )
682    }
683
684    // -------------------------------------------------------------------------
685    // Blueprint
686    // -------------------------------------------------------------------------
687
688    /// Fetch the blueprint template for this course.
689    ///
690    /// `template_id` is typically `"default"` or a numeric ID.
691    ///
692    /// # Canvas API
693    /// `GET /api/v1/courses/:course_id/blueprint_templates/:template_id`
694    pub async fn get_blueprint(&self, template_id: &str) -> Result<BlueprintTemplate> {
695        let mut tmpl: BlueprintTemplate = self
696            .req()
697            .get(
698                &format!("courses/{}/blueprint_templates/{template_id}", self.id),
699                &[],
700            )
701            .await?;
702        tmpl.requester = self.requester.clone();
703        Ok(tmpl)
704    }
705
706    /// Stream blueprint subscriptions for this (child) course.
707    ///
708    /// # Canvas API
709    /// `GET /api/v1/courses/:course_id/blueprint_subscriptions`
710    pub fn get_blueprint_subscriptions(&self) -> PageStream<BlueprintSubscription> {
711        let course_id = self.id;
712        PageStream::new_with_injector(
713            Arc::clone(self.req()),
714            &format!("courses/{course_id}/blueprint_subscriptions"),
715            vec![],
716            |mut s: BlueprintSubscription, req| {
717                s.requester = Some(Arc::clone(&req));
718                s
719            },
720        )
721    }
722
723    // -------------------------------------------------------------------------
724    // Content Migrations
725    // -------------------------------------------------------------------------
726
727    /// Fetch a single content migration by ID.
728    ///
729    /// # Canvas API
730    /// `GET /api/v1/courses/:course_id/content_migrations/:id`
731    pub async fn get_content_migration(&self, migration_id: u64) -> Result<ContentMigration> {
732        let mut migration: ContentMigration = self
733            .req()
734            .get(
735                &format!("courses/{}/content_migrations/{migration_id}", self.id),
736                &[],
737            )
738            .await?;
739        migration.requester = self.requester.clone();
740        Ok(migration)
741    }
742
743    /// Stream all content migrations for this course.
744    ///
745    /// # Canvas API
746    /// `GET /api/v1/courses/:course_id/content_migrations`
747    pub fn get_content_migrations(&self) -> PageStream<ContentMigration> {
748        let course_id = self.id;
749        PageStream::new_with_injector(
750            Arc::clone(self.req()),
751            &format!("courses/{course_id}/content_migrations"),
752            vec![],
753            |mut m: ContentMigration, req| {
754                m.requester = Some(Arc::clone(&req));
755                m
756            },
757        )
758    }
759
760    /// Create a content migration for this course.
761    ///
762    /// # Canvas API
763    /// `POST /api/v1/courses/:course_id/content_migrations`
764    pub async fn create_content_migration(
765        &self,
766        migration_type: &str,
767        params: &[(String, String)],
768    ) -> Result<ContentMigration> {
769        let mut form = vec![("migration_type".to_string(), migration_type.to_string())];
770        form.extend_from_slice(params);
771        let mut migration: ContentMigration = self
772            .req()
773            .post(&format!("courses/{}/content_migrations", self.id), &form)
774            .await?;
775        migration.requester = self.requester.clone();
776        Ok(migration)
777    }
778
779    /// Stream available content migration types for this course.
780    ///
781    /// # Canvas API
782    /// `GET /api/v1/courses/:course_id/content_migrations/migrators`
783    pub fn get_migrators(&self) -> PageStream<Migrator> {
784        PageStream::new(
785            Arc::clone(self.req()),
786            &format!("courses/{}/content_migrations/migrators", self.id),
787            vec![],
788        )
789    }
790
791    // -------------------------------------------------------------------------
792    // Outcome Groups
793    // -------------------------------------------------------------------------
794
795    /// Stream all outcome group links for this course.
796    ///
797    /// # Canvas API
798    /// `GET /api/v1/courses/:course_id/outcome_group_links`
799    pub fn get_outcome_group_links(&self) -> PageStream<OutcomeLink> {
800        PageStream::new(
801            Arc::clone(self.req()),
802            &format!("courses/{}/outcome_group_links", self.id),
803            vec![],
804        )
805    }
806
807    /// Fetch the root outcome group for this course.
808    ///
809    /// # Canvas API
810    /// `GET /api/v1/courses/:course_id/root_outcome_group`
811    pub async fn get_root_outcome_group(&self) -> Result<OutcomeGroup> {
812        let mut group: OutcomeGroup = self
813            .req()
814            .get(&format!("courses/{}/root_outcome_group", self.id), &[])
815            .await?;
816        group.requester = self.requester.clone();
817        Ok(group)
818    }
819
820    /// Fetch a single outcome group by ID.
821    ///
822    /// # Canvas API
823    /// `GET /api/v1/courses/:course_id/outcome_groups/:id`
824    pub async fn get_outcome_group(&self, group_id: u64) -> Result<OutcomeGroup> {
825        let mut group: OutcomeGroup = self
826            .req()
827            .get(
828                &format!("courses/{}/outcome_groups/{group_id}", self.id),
829                &[],
830            )
831            .await?;
832        group.requester = self.requester.clone();
833        Ok(group)
834    }
835
836    /// Create a top-level outcome group on this course.
837    ///
838    /// # Canvas API
839    /// `POST /api/v1/courses/:course_id/outcome_groups`
840    pub async fn create_outcome_group(
841        &self,
842        params: UpdateOutcomeGroupParams,
843    ) -> Result<OutcomeGroup> {
844        let form = wrap_params("outcome_group", &params);
845        let mut group: OutcomeGroup = self
846            .req()
847            .post(&format!("courses/{}/outcome_groups", self.id), &form)
848            .await?;
849        group.requester = self.requester.clone();
850        Ok(group)
851    }
852
853    // -------------------------------------------------------------------------
854    // Custom Gradebook Columns
855    // -------------------------------------------------------------------------
856
857    /// Stream all custom gradebook columns for this course.
858    ///
859    /// # Canvas API
860    /// `GET /api/v1/courses/:id/custom_gradebook_columns`
861    pub fn get_custom_columns(&self) -> PageStream<CustomGradebookColumn> {
862        let course_id = self.id;
863        PageStream::new_with_injector(
864            Arc::clone(self.req()),
865            &format!("courses/{course_id}/custom_gradebook_columns"),
866            vec![],
867            move |mut col: CustomGradebookColumn, req| {
868                col.requester = Some(Arc::clone(&req));
869                col.course_id = Some(course_id);
870                col
871            },
872        )
873    }
874
875    /// Create a new custom gradebook column in this course.
876    ///
877    /// # Canvas API
878    /// `POST /api/v1/courses/:id/custom_gradebook_columns`
879    pub async fn create_custom_column(
880        &self,
881        params: CustomGradebookColumnParams,
882    ) -> Result<CustomGradebookColumn> {
883        let form = wrap_params("column", &params);
884        let mut col: CustomGradebookColumn = self
885            .req()
886            .post(
887                &format!("courses/{}/custom_gradebook_columns", self.id),
888                &form,
889            )
890            .await?;
891        col.requester = self.requester.clone();
892        col.course_id = Some(self.id);
893        Ok(col)
894    }
895
896    // -------------------------------------------------------------------------
897    // Gradebook History
898    // -------------------------------------------------------------------------
899
900    /// Stream the days for which there is gradebook history in this course.
901    ///
902    /// # Canvas API
903    /// `GET /api/v1/courses/:course_id/gradebook_history/days`
904    pub fn get_gradebook_history_dates(&self) -> PageStream<Day> {
905        PageStream::new(
906            Arc::clone(self.req()),
907            &format!("courses/{}/gradebook_history/days", self.id),
908            vec![],
909        )
910    }
911
912    /// Stream graders who worked in this course on a given date.
913    ///
914    /// `date` should be formatted as `YYYY-MM-DD`.
915    ///
916    /// # Canvas API
917    /// `GET /api/v1/courses/:course_id/gradebook_history/:date`
918    pub fn get_gradebook_history_details(&self, date: &str) -> PageStream<Grader> {
919        PageStream::new(
920            Arc::clone(self.req()),
921            &format!("courses/{}/gradebook_history/{date}", self.id),
922            vec![],
923        )
924    }
925
926    /// Stream submission versions graded by a specific grader on a specific assignment and date.
927    ///
928    /// # Canvas API
929    /// `GET /api/v1/courses/:course_id/gradebook_history/:date/graders/:grader_id/assignments/:assignment_id/submissions`
930    pub fn get_submission_history(
931        &self,
932        date: &str,
933        grader_id: u64,
934        assignment_id: u64,
935    ) -> PageStream<SubmissionHistory> {
936        PageStream::new(
937            Arc::clone(self.req()),
938            &format!(
939                "courses/{}/gradebook_history/{date}/graders/{grader_id}/assignments/{assignment_id}/submissions",
940                self.id
941            ),
942            vec![],
943        )
944    }
945
946    /// Stream all submission versions (uncollated) for this course.
947    ///
948    /// # Canvas API
949    /// `GET /api/v1/courses/:course_id/gradebook_history/feed`
950    pub fn get_uncollated_submissions(&self) -> PageStream<SubmissionVersion> {
951        PageStream::new(
952            Arc::clone(self.req()),
953            &format!("courses/{}/gradebook_history/feed", self.id),
954            vec![],
955        )
956    }
957
958    // -------------------------------------------------------------------------
959    // New Quizzes (feature = "new-quizzes")
960    // -------------------------------------------------------------------------
961
962    /// Fetch a single New Quiz by its assignment ID.
963    ///
964    /// # Canvas API
965    /// `GET /api/quiz/v1/courses/:course_id/quizzes/:assignment_id`
966    #[cfg(feature = "new-quizzes")]
967    pub async fn get_new_quiz(
968        &self,
969        assignment_id: &str,
970    ) -> Result<crate::resources::new_quiz::NewQuiz> {
971        let mut quiz: crate::resources::new_quiz::NewQuiz = self
972            .req()
973            .nq_get(&format!("courses/{}/quizzes/{assignment_id}", self.id), &[])
974            .await?;
975        quiz.requester = self.requester.clone();
976        quiz.course_id = Some(self.id);
977        Ok(quiz)
978    }
979
980    /// Stream all New Quizzes for this course.
981    ///
982    /// # Canvas API
983    /// `GET /api/quiz/v1/courses/:course_id/quizzes`
984    #[cfg(feature = "new-quizzes")]
985    pub fn get_new_quizzes(&self) -> PageStream<crate::resources::new_quiz::NewQuiz> {
986        let course_id = self.id;
987        PageStream::new_with_injector_nq(
988            Arc::clone(self.req()),
989            &format!("courses/{course_id}/quizzes"),
990            vec![],
991            move |mut q: crate::resources::new_quiz::NewQuiz, req| {
992                q.requester = Some(Arc::clone(&req));
993                q.course_id = Some(course_id);
994                q
995            },
996        )
997    }
998
999    /// Create a New Quiz in this course.
1000    ///
1001    /// # Canvas API
1002    /// `POST /api/quiz/v1/courses/:course_id/quizzes`
1003    #[cfg(feature = "new-quizzes")]
1004    pub async fn create_new_quiz(
1005        &self,
1006        params: crate::resources::new_quiz::NewQuizParams,
1007    ) -> Result<crate::resources::new_quiz::NewQuiz> {
1008        let body = serde_json::to_value(&params).unwrap_or_default();
1009        let mut quiz: crate::resources::new_quiz::NewQuiz = self
1010            .req()
1011            .nq_post(&format!("courses/{}/quizzes", self.id), &body)
1012            .await?;
1013        quiz.requester = self.requester.clone();
1014        quiz.course_id = Some(self.id);
1015        Ok(quiz)
1016    }
1017
1018    // -------------------------------------------------------------------------
1019    // Grading Periods
1020    // -------------------------------------------------------------------------
1021
1022    /// Stream all grading periods for this course.
1023    ///
1024    /// # Canvas API
1025    /// `GET /api/v1/courses/:course_id/grading_periods`
1026    pub fn get_grading_periods(&self) -> PageStream<GradingPeriod> {
1027        let course_id = self.id;
1028        PageStream::new_with_injector(
1029            Arc::clone(self.req()),
1030            &format!("courses/{course_id}/grading_periods"),
1031            vec![],
1032            move |mut gp: GradingPeriod, req| {
1033                gp.requester = Some(Arc::clone(&req));
1034                gp.course_id = Some(course_id);
1035                gp
1036            },
1037        )
1038    }
1039
1040    // -------------------------------------------------------------------------
1041    // Grading Standards
1042    // -------------------------------------------------------------------------
1043
1044    /// Stream all grading standards for this course.
1045    ///
1046    /// # Canvas API
1047    /// `GET /api/v1/courses/:course_id/grading_standards`
1048    pub fn get_grading_standards(&self) -> PageStream<GradingStandard> {
1049        PageStream::new(
1050            Arc::clone(self.req()),
1051            &format!("courses/{}/grading_standards", self.id),
1052            vec![],
1053        )
1054    }
1055
1056    /// Create a grading standard for this course.
1057    ///
1058    /// # Canvas API
1059    /// `POST /api/v1/courses/:course_id/grading_standards`
1060    pub async fn create_grading_standard(
1061        &self,
1062        params: crate::resources::grading_standard::GradingStandardParams,
1063    ) -> Result<GradingStandard> {
1064        let form = wrap_params("grading_scheme_entry", &params.grading_scheme_entry)
1065            .into_iter()
1066            .chain([("title".into(), params.title)])
1067            .collect::<Vec<_>>();
1068        self.req()
1069            .post(&format!("courses/{}/grading_standards", self.id), &form)
1070            .await
1071    }
1072
1073    // -------------------------------------------------------------------------
1074    // Content Exports
1075    // -------------------------------------------------------------------------
1076
1077    /// Fetch a single content export by ID.
1078    ///
1079    /// # Canvas API
1080    /// `GET /api/v1/courses/:course_id/content_exports/:id`
1081    pub async fn get_content_export(&self, export_id: u64) -> Result<ContentExport> {
1082        self.req()
1083            .get(
1084                &format!("courses/{}/content_exports/{export_id}", self.id),
1085                &[],
1086            )
1087            .await
1088    }
1089
1090    /// Stream all content exports for this course.
1091    ///
1092    /// # Canvas API
1093    /// `GET /api/v1/courses/:course_id/content_exports`
1094    pub fn get_content_exports(&self) -> PageStream<ContentExport> {
1095        PageStream::new(
1096            Arc::clone(self.req()),
1097            &format!("courses/{}/content_exports", self.id),
1098            vec![],
1099        )
1100    }
1101
1102    /// Create a content export for this course.
1103    ///
1104    /// # Canvas API
1105    /// `POST /api/v1/courses/:course_id/content_exports`
1106    pub async fn create_content_export(
1107        &self,
1108        params: ContentExportParams,
1109    ) -> Result<ContentExport> {
1110        let form = vec![
1111            ("export_type".into(), params.export_type),
1112            (
1113                "skip_notifications".into(),
1114                params.skip_notifications.unwrap_or(false).to_string(),
1115            ),
1116        ];
1117        self.req()
1118            .post(&format!("courses/{}/content_exports", self.id), &form)
1119            .await
1120    }
1121
1122    // -------------------------------------------------------------------------
1123    // Grade Change Log
1124    // -------------------------------------------------------------------------
1125
1126    /// Stream grade change audit events for this course.
1127    ///
1128    /// The Canvas API wraps the array in `{ "events": [...] }`; `PageStream`
1129    /// handles this automatically.
1130    ///
1131    /// # Canvas API
1132    /// `GET /api/v1/audit/grade_change/courses/:course_id`
1133    pub fn get_grade_change_events(&self) -> PageStream<GradeChangeEvent> {
1134        PageStream::new(
1135            Arc::clone(self.req()),
1136            &format!("audit/grade_change/courses/{}", self.id),
1137            vec![],
1138        )
1139    }
1140
1141    // -------------------------------------------------------------------------
1142    // Features
1143    // -------------------------------------------------------------------------
1144
1145    /// Stream all feature flags for this course.
1146    ///
1147    /// # Canvas API
1148    /// `GET /api/v1/courses/:course_id/features`
1149    pub fn get_features(&self) -> PageStream<Feature> {
1150        PageStream::new(
1151            Arc::clone(self.req()),
1152            &format!("courses/{}/features", self.id),
1153            vec![],
1154        )
1155    }
1156
1157    /// Fetch a specific feature flag for this course by feature name.
1158    ///
1159    /// # Canvas API
1160    /// `GET /api/v1/courses/:course_id/features/flags/:feature`
1161    pub async fn get_feature_flag(&self, feature: &str) -> Result<FeatureFlag> {
1162        let mut ff: FeatureFlag = self
1163            .req()
1164            .get(
1165                &format!("courses/{}/features/flags/{feature}", self.id),
1166                &[],
1167            )
1168            .await?;
1169        ff.requester = self.requester.clone();
1170        Ok(ff)
1171    }
1172
1173    /// List all enabled feature names for this course.
1174    ///
1175    /// # Canvas API
1176    /// `GET /api/v1/courses/:course_id/features/enabled`
1177    pub async fn get_enabled_features(&self) -> Result<Vec<String>> {
1178        self.req()
1179            .get(&format!("courses/{}/features/enabled", self.id), &[])
1180            .await
1181    }
1182
1183    // -------------------------------------------------------------------------
1184    // LTI Resource Links
1185    // -------------------------------------------------------------------------
1186
1187    /// Stream all LTI resource links in this course.
1188    ///
1189    /// # Canvas API
1190    /// `GET /api/v1/courses/:course_id/lti_resource_links`
1191    pub fn get_lti_resource_links(&self) -> PageStream<LtiResourceLink> {
1192        PageStream::new(
1193            Arc::clone(self.req()),
1194            &format!("courses/{}/lti_resource_links", self.id),
1195            vec![],
1196        )
1197    }
1198
1199    /// Fetch a single LTI resource link.
1200    ///
1201    /// # Canvas API
1202    /// `GET /api/v1/courses/:course_id/lti_resource_links/:id`
1203    pub async fn get_lti_resource_link(&self, link_id: u64) -> Result<LtiResourceLink> {
1204        self.req()
1205            .get(
1206                &format!("courses/{}/lti_resource_links/{link_id}", self.id),
1207                &[],
1208            )
1209            .await
1210    }
1211
1212    /// Create a new LTI resource link in this course.
1213    ///
1214    /// # Canvas API
1215    /// `POST /api/v1/courses/:course_id/lti_resource_links`
1216    pub async fn create_lti_resource_link(
1217        &self,
1218        params: CreateLtiResourceLinkParams,
1219    ) -> Result<LtiResourceLink> {
1220        let form = crate::params::flatten_params(&serde_json::to_value(&params).unwrap());
1221        self.req()
1222            .post(&format!("courses/{}/lti_resource_links", self.id), &form)
1223            .await
1224    }
1225
1226    /// Conclude (soft-delete) this course.
1227    ///
1228    /// # Canvas API
1229    /// `DELETE /api/v1/courses/:id?event=conclude`
1230    pub async fn conclude(&self) -> Result<serde_json::Value> {
1231        self.req()
1232            .delete(
1233                &format!("courses/{}", self.id),
1234                &[("event".to_string(), "conclude".to_string())],
1235            )
1236            .await
1237    }
1238
1239    /// Reset this course to a blank state (removes all content).
1240    ///
1241    /// # Canvas API
1242    /// `POST /api/v1/courses/:id/reset_content`
1243    pub async fn reset(&self) -> Result<Course> {
1244        let mut c: Course = self
1245            .req()
1246            .post(&format!("courses/{}/reset_content", self.id), &[])
1247            .await?;
1248        c.requester = Some(Arc::clone(self.req()));
1249        Ok(c)
1250    }
1251
1252    /// Get this course's settings.
1253    ///
1254    /// # Canvas API
1255    /// `GET /api/v1/courses/:id/settings`
1256    pub async fn get_settings(&self) -> Result<serde_json::Value> {
1257        self.req()
1258            .get(&format!("courses/{}/settings", self.id), &[])
1259            .await
1260    }
1261
1262    /// Update this course's settings.
1263    ///
1264    /// # Canvas API
1265    /// `PUT /api/v1/courses/:id/settings`
1266    pub async fn update_settings(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
1267        self.req()
1268            .put(&format!("courses/{}/settings", self.id), params)
1269            .await
1270    }
1271
1272    /// Get the late policy for this course.
1273    ///
1274    /// # Canvas API
1275    /// `GET /api/v1/courses/:id/late_policy`
1276    pub async fn get_late_policy(&self) -> Result<serde_json::Value> {
1277        self.req()
1278            .get(&format!("courses/{}/late_policy", self.id), &[])
1279            .await
1280    }
1281
1282    /// Stream multiple submissions for this course.
1283    ///
1284    /// # Canvas API
1285    /// `GET /api/v1/courses/:id/students/submissions`
1286    pub fn get_multiple_submissions(&self) -> PageStream<crate::resources::submission::Submission> {
1287        let course_id = self.id;
1288        PageStream::new_with_injector(
1289            Arc::clone(self.req()),
1290            &format!("courses/{}/students/submissions", self.id),
1291            vec![],
1292            move |mut s: crate::resources::submission::Submission, req| {
1293                s.requester = Some(Arc::clone(&req));
1294                s.course_id = Some(course_id);
1295                s
1296            },
1297        )
1298    }
1299
1300    /// Enroll a user in this course.
1301    ///
1302    /// # Canvas API
1303    /// `POST /api/v1/courses/:id/enrollments`
1304    pub async fn enroll_user(
1305        &self,
1306        user_id: u64,
1307        enrollment_type: &str,
1308    ) -> Result<crate::resources::enrollment::Enrollment> {
1309        let params = vec![
1310            ("enrollment[user_id]".to_string(), user_id.to_string()),
1311            ("enrollment[type]".to_string(), enrollment_type.to_string()),
1312        ];
1313        let mut e: crate::resources::enrollment::Enrollment = self
1314            .req()
1315            .post(&format!("courses/{}/enrollments", self.id), &params)
1316            .await?;
1317        e.requester = Some(Arc::clone(self.req()));
1318        Ok(e)
1319    }
1320
1321    /// Bulk-update grades for this course asynchronously.
1322    ///
1323    /// # Canvas API
1324    /// `POST /api/v1/courses/:id/submissions/update_grades`
1325    pub async fn submissions_bulk_update(
1326        &self,
1327        params: &[(String, String)],
1328    ) -> Result<crate::resources::progress::Progress> {
1329        let mut p: crate::resources::progress::Progress = self
1330            .req()
1331            .post(
1332                &format!("courses/{}/submissions/update_grades", self.id),
1333                params,
1334            )
1335            .await?;
1336        p.requester = Some(Arc::clone(self.req()));
1337        Ok(p)
1338    }
1339}