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, OutcomeImport, 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    /// Create a rubric association for this course.
668    ///
669    /// # Canvas API
670    /// `POST /api/v1/courses/:course_id/rubric_associations`
671    pub async fn create_rubric_association(
672        &self,
673        params: &[(String, String)],
674    ) -> Result<RubricAssociation> {
675        let mut assoc: RubricAssociation = self
676            .req()
677            .post(&format!("courses/{}/rubric_associations", self.id), params)
678            .await?;
679        assoc.requester = self.requester.clone();
680        assoc.course_id = Some(self.id);
681        Ok(assoc)
682    }
683
684    /// Stream all rubric associations for this course.
685    ///
686    /// # Canvas API
687    /// `GET /api/v1/courses/:course_id/rubric_associations`
688    pub fn get_rubric_associations(&self) -> PageStream<RubricAssociation> {
689        let course_id = self.id;
690        PageStream::new_with_injector(
691            Arc::clone(self.req()),
692            &format!("courses/{course_id}/rubric_associations"),
693            vec![],
694            |mut a: RubricAssociation, req| {
695                a.requester = Some(Arc::clone(&req));
696                a
697            },
698        )
699    }
700
701    // -------------------------------------------------------------------------
702    // Blueprint
703    // -------------------------------------------------------------------------
704
705    /// Fetch the blueprint template for this course.
706    ///
707    /// `template_id` is typically `"default"` or a numeric ID.
708    ///
709    /// # Canvas API
710    /// `GET /api/v1/courses/:course_id/blueprint_templates/:template_id`
711    pub async fn get_blueprint(&self, template_id: &str) -> Result<BlueprintTemplate> {
712        let mut tmpl: BlueprintTemplate = self
713            .req()
714            .get(
715                &format!("courses/{}/blueprint_templates/{template_id}", self.id),
716                &[],
717            )
718            .await?;
719        tmpl.requester = self.requester.clone();
720        Ok(tmpl)
721    }
722
723    /// Stream blueprint subscriptions for this (child) course.
724    ///
725    /// # Canvas API
726    /// `GET /api/v1/courses/:course_id/blueprint_subscriptions`
727    pub fn get_blueprint_subscriptions(&self) -> PageStream<BlueprintSubscription> {
728        let course_id = self.id;
729        PageStream::new_with_injector(
730            Arc::clone(self.req()),
731            &format!("courses/{course_id}/blueprint_subscriptions"),
732            vec![],
733            |mut s: BlueprintSubscription, req| {
734                s.requester = Some(Arc::clone(&req));
735                s
736            },
737        )
738    }
739
740    // -------------------------------------------------------------------------
741    // Content Migrations
742    // -------------------------------------------------------------------------
743
744    /// Fetch a single content migration by ID.
745    ///
746    /// # Canvas API
747    /// `GET /api/v1/courses/:course_id/content_migrations/:id`
748    pub async fn get_content_migration(&self, migration_id: u64) -> Result<ContentMigration> {
749        let mut migration: ContentMigration = self
750            .req()
751            .get(
752                &format!("courses/{}/content_migrations/{migration_id}", self.id),
753                &[],
754            )
755            .await?;
756        migration.requester = self.requester.clone();
757        Ok(migration)
758    }
759
760    /// Stream all content migrations for this course.
761    ///
762    /// # Canvas API
763    /// `GET /api/v1/courses/:course_id/content_migrations`
764    pub fn get_content_migrations(&self) -> PageStream<ContentMigration> {
765        let course_id = self.id;
766        PageStream::new_with_injector(
767            Arc::clone(self.req()),
768            &format!("courses/{course_id}/content_migrations"),
769            vec![],
770            |mut m: ContentMigration, req| {
771                m.requester = Some(Arc::clone(&req));
772                m
773            },
774        )
775    }
776
777    /// Create a content migration for this course.
778    ///
779    /// # Canvas API
780    /// `POST /api/v1/courses/:course_id/content_migrations`
781    pub async fn create_content_migration(
782        &self,
783        migration_type: &str,
784        params: &[(String, String)],
785    ) -> Result<ContentMigration> {
786        let mut form = vec![("migration_type".to_string(), migration_type.to_string())];
787        form.extend_from_slice(params);
788        let mut migration: ContentMigration = self
789            .req()
790            .post(&format!("courses/{}/content_migrations", self.id), &form)
791            .await?;
792        migration.requester = self.requester.clone();
793        Ok(migration)
794    }
795
796    /// Stream available content migration types for this course.
797    ///
798    /// # Canvas API
799    /// `GET /api/v1/courses/:course_id/content_migrations/migrators`
800    pub fn get_migrators(&self) -> PageStream<Migrator> {
801        PageStream::new(
802            Arc::clone(self.req()),
803            &format!("courses/{}/content_migrations/migrators", self.id),
804            vec![],
805        )
806    }
807
808    // -------------------------------------------------------------------------
809    // Outcome Groups
810    // -------------------------------------------------------------------------
811
812    /// Stream all outcome group links for this course.
813    ///
814    /// # Canvas API
815    /// `GET /api/v1/courses/:course_id/outcome_group_links`
816    pub fn get_outcome_group_links(&self) -> PageStream<OutcomeLink> {
817        PageStream::new(
818            Arc::clone(self.req()),
819            &format!("courses/{}/outcome_group_links", self.id),
820            vec![],
821        )
822    }
823
824    /// Fetch the root outcome group for this course.
825    ///
826    /// # Canvas API
827    /// `GET /api/v1/courses/:course_id/root_outcome_group`
828    pub async fn get_root_outcome_group(&self) -> Result<OutcomeGroup> {
829        let mut group: OutcomeGroup = self
830            .req()
831            .get(&format!("courses/{}/root_outcome_group", self.id), &[])
832            .await?;
833        group.requester = self.requester.clone();
834        Ok(group)
835    }
836
837    /// Fetch a single outcome group by ID.
838    ///
839    /// # Canvas API
840    /// `GET /api/v1/courses/:course_id/outcome_groups/:id`
841    pub async fn get_outcome_group(&self, group_id: u64) -> Result<OutcomeGroup> {
842        let mut group: OutcomeGroup = self
843            .req()
844            .get(
845                &format!("courses/{}/outcome_groups/{group_id}", self.id),
846                &[],
847            )
848            .await?;
849        group.requester = self.requester.clone();
850        Ok(group)
851    }
852
853    /// Create a top-level outcome group on this course.
854    ///
855    /// # Canvas API
856    /// `POST /api/v1/courses/:course_id/outcome_groups`
857    pub async fn create_outcome_group(
858        &self,
859        params: UpdateOutcomeGroupParams,
860    ) -> Result<OutcomeGroup> {
861        let form = wrap_params("outcome_group", &params);
862        let mut group: OutcomeGroup = self
863            .req()
864            .post(&format!("courses/{}/outcome_groups", self.id), &form)
865            .await?;
866        group.requester = self.requester.clone();
867        Ok(group)
868    }
869
870    // -------------------------------------------------------------------------
871    // Custom Gradebook Columns
872    // -------------------------------------------------------------------------
873
874    /// Stream all custom gradebook columns for this course.
875    ///
876    /// # Canvas API
877    /// `GET /api/v1/courses/:id/custom_gradebook_columns`
878    pub fn get_custom_columns(&self) -> PageStream<CustomGradebookColumn> {
879        let course_id = self.id;
880        PageStream::new_with_injector(
881            Arc::clone(self.req()),
882            &format!("courses/{course_id}/custom_gradebook_columns"),
883            vec![],
884            move |mut col: CustomGradebookColumn, req| {
885                col.requester = Some(Arc::clone(&req));
886                col.course_id = Some(course_id);
887                col
888            },
889        )
890    }
891
892    /// Create a new custom gradebook column in this course.
893    ///
894    /// # Canvas API
895    /// `POST /api/v1/courses/:id/custom_gradebook_columns`
896    pub async fn create_custom_column(
897        &self,
898        params: CustomGradebookColumnParams,
899    ) -> Result<CustomGradebookColumn> {
900        let form = wrap_params("column", &params);
901        let mut col: CustomGradebookColumn = self
902            .req()
903            .post(
904                &format!("courses/{}/custom_gradebook_columns", self.id),
905                &form,
906            )
907            .await?;
908        col.requester = self.requester.clone();
909        col.course_id = Some(self.id);
910        Ok(col)
911    }
912
913    // -------------------------------------------------------------------------
914    // Gradebook History
915    // -------------------------------------------------------------------------
916
917    /// Stream the days for which there is gradebook history in this course.
918    ///
919    /// # Canvas API
920    /// `GET /api/v1/courses/:course_id/gradebook_history/days`
921    pub fn get_gradebook_history_dates(&self) -> PageStream<Day> {
922        PageStream::new(
923            Arc::clone(self.req()),
924            &format!("courses/{}/gradebook_history/days", self.id),
925            vec![],
926        )
927    }
928
929    /// Stream graders who worked in this course on a given date.
930    ///
931    /// `date` should be formatted as `YYYY-MM-DD`.
932    ///
933    /// # Canvas API
934    /// `GET /api/v1/courses/:course_id/gradebook_history/:date`
935    pub fn get_gradebook_history_details(&self, date: &str) -> PageStream<Grader> {
936        PageStream::new(
937            Arc::clone(self.req()),
938            &format!("courses/{}/gradebook_history/{date}", self.id),
939            vec![],
940        )
941    }
942
943    /// Stream submission versions graded by a specific grader on a specific assignment and date.
944    ///
945    /// # Canvas API
946    /// `GET /api/v1/courses/:course_id/gradebook_history/:date/graders/:grader_id/assignments/:assignment_id/submissions`
947    pub fn get_submission_history(
948        &self,
949        date: &str,
950        grader_id: u64,
951        assignment_id: u64,
952    ) -> PageStream<SubmissionHistory> {
953        PageStream::new(
954            Arc::clone(self.req()),
955            &format!(
956                "courses/{}/gradebook_history/{date}/graders/{grader_id}/assignments/{assignment_id}/submissions",
957                self.id
958            ),
959            vec![],
960        )
961    }
962
963    /// Stream all submission versions (uncollated) for this course.
964    ///
965    /// # Canvas API
966    /// `GET /api/v1/courses/:course_id/gradebook_history/feed`
967    pub fn get_uncollated_submissions(&self) -> PageStream<SubmissionVersion> {
968        PageStream::new(
969            Arc::clone(self.req()),
970            &format!("courses/{}/gradebook_history/feed", self.id),
971            vec![],
972        )
973    }
974
975    // -------------------------------------------------------------------------
976    // New Quizzes (feature = "new-quizzes")
977    // -------------------------------------------------------------------------
978
979    /// Fetch a single New Quiz by its assignment ID.
980    ///
981    /// # Canvas API
982    /// `GET /api/quiz/v1/courses/:course_id/quizzes/:assignment_id`
983    #[cfg(feature = "new-quizzes")]
984    pub async fn get_new_quiz(
985        &self,
986        assignment_id: &str,
987    ) -> Result<crate::resources::new_quiz::NewQuiz> {
988        let mut quiz: crate::resources::new_quiz::NewQuiz = self
989            .req()
990            .nq_get(&format!("courses/{}/quizzes/{assignment_id}", self.id), &[])
991            .await?;
992        quiz.requester = self.requester.clone();
993        quiz.course_id = Some(self.id);
994        Ok(quiz)
995    }
996
997    /// Stream all New Quizzes for this course.
998    ///
999    /// # Canvas API
1000    /// `GET /api/quiz/v1/courses/:course_id/quizzes`
1001    #[cfg(feature = "new-quizzes")]
1002    pub fn get_new_quizzes(&self) -> PageStream<crate::resources::new_quiz::NewQuiz> {
1003        let course_id = self.id;
1004        PageStream::new_with_injector_nq(
1005            Arc::clone(self.req()),
1006            &format!("courses/{course_id}/quizzes"),
1007            vec![],
1008            move |mut q: crate::resources::new_quiz::NewQuiz, req| {
1009                q.requester = Some(Arc::clone(&req));
1010                q.course_id = Some(course_id);
1011                q
1012            },
1013        )
1014    }
1015
1016    /// Create a New Quiz in this course.
1017    ///
1018    /// # Canvas API
1019    /// `POST /api/quiz/v1/courses/:course_id/quizzes`
1020    #[cfg(feature = "new-quizzes")]
1021    pub async fn create_new_quiz(
1022        &self,
1023        params: crate::resources::new_quiz::NewQuizParams,
1024    ) -> Result<crate::resources::new_quiz::NewQuiz> {
1025        let body = serde_json::to_value(&params).unwrap_or_default();
1026        let mut quiz: crate::resources::new_quiz::NewQuiz = self
1027            .req()
1028            .nq_post(&format!("courses/{}/quizzes", self.id), &body)
1029            .await?;
1030        quiz.requester = self.requester.clone();
1031        quiz.course_id = Some(self.id);
1032        Ok(quiz)
1033    }
1034
1035    // -------------------------------------------------------------------------
1036    // Grading Periods
1037    // -------------------------------------------------------------------------
1038
1039    /// Stream all grading periods for this course.
1040    ///
1041    /// # Canvas API
1042    /// `GET /api/v1/courses/:course_id/grading_periods`
1043    pub fn get_grading_periods(&self) -> PageStream<GradingPeriod> {
1044        let course_id = self.id;
1045        PageStream::new_with_injector(
1046            Arc::clone(self.req()),
1047            &format!("courses/{course_id}/grading_periods"),
1048            vec![],
1049            move |mut gp: GradingPeriod, req| {
1050                gp.requester = Some(Arc::clone(&req));
1051                gp.course_id = Some(course_id);
1052                gp
1053            },
1054        )
1055    }
1056
1057    // -------------------------------------------------------------------------
1058    // Grading Standards
1059    // -------------------------------------------------------------------------
1060
1061    /// Stream all grading standards for this course.
1062    ///
1063    /// # Canvas API
1064    /// `GET /api/v1/courses/:course_id/grading_standards`
1065    pub fn get_grading_standards(&self) -> PageStream<GradingStandard> {
1066        PageStream::new(
1067            Arc::clone(self.req()),
1068            &format!("courses/{}/grading_standards", self.id),
1069            vec![],
1070        )
1071    }
1072
1073    /// Create a grading standard for this course.
1074    ///
1075    /// # Canvas API
1076    /// `POST /api/v1/courses/:course_id/grading_standards`
1077    pub async fn create_grading_standard(
1078        &self,
1079        params: crate::resources::grading_standard::GradingStandardParams,
1080    ) -> Result<GradingStandard> {
1081        let form = wrap_params("grading_scheme_entry", &params.grading_scheme_entry)
1082            .into_iter()
1083            .chain([("title".into(), params.title)])
1084            .collect::<Vec<_>>();
1085        self.req()
1086            .post(&format!("courses/{}/grading_standards", self.id), &form)
1087            .await
1088    }
1089
1090    // -------------------------------------------------------------------------
1091    // Content Exports
1092    // -------------------------------------------------------------------------
1093
1094    /// Fetch a single content export by ID.
1095    ///
1096    /// # Canvas API
1097    /// `GET /api/v1/courses/:course_id/content_exports/:id`
1098    pub async fn get_content_export(&self, export_id: u64) -> Result<ContentExport> {
1099        self.req()
1100            .get(
1101                &format!("courses/{}/content_exports/{export_id}", self.id),
1102                &[],
1103            )
1104            .await
1105    }
1106
1107    /// Stream all content exports for this course.
1108    ///
1109    /// # Canvas API
1110    /// `GET /api/v1/courses/:course_id/content_exports`
1111    pub fn get_content_exports(&self) -> PageStream<ContentExport> {
1112        PageStream::new(
1113            Arc::clone(self.req()),
1114            &format!("courses/{}/content_exports", self.id),
1115            vec![],
1116        )
1117    }
1118
1119    /// Create a content export for this course.
1120    ///
1121    /// # Canvas API
1122    /// `POST /api/v1/courses/:course_id/content_exports`
1123    pub async fn create_content_export(
1124        &self,
1125        params: ContentExportParams,
1126    ) -> Result<ContentExport> {
1127        let form = vec![
1128            ("export_type".into(), params.export_type),
1129            (
1130                "skip_notifications".into(),
1131                params.skip_notifications.unwrap_or(false).to_string(),
1132            ),
1133        ];
1134        self.req()
1135            .post(&format!("courses/{}/content_exports", self.id), &form)
1136            .await
1137    }
1138
1139    // -------------------------------------------------------------------------
1140    // Grade Change Log
1141    // -------------------------------------------------------------------------
1142
1143    /// Stream grade change audit events for this course.
1144    ///
1145    /// The Canvas API wraps the array in `{ "events": [...] }`; `PageStream`
1146    /// handles this automatically.
1147    ///
1148    /// # Canvas API
1149    /// `GET /api/v1/audit/grade_change/courses/:course_id`
1150    pub fn get_grade_change_events(&self) -> PageStream<GradeChangeEvent> {
1151        PageStream::new(
1152            Arc::clone(self.req()),
1153            &format!("audit/grade_change/courses/{}", self.id),
1154            vec![],
1155        )
1156    }
1157
1158    // -------------------------------------------------------------------------
1159    // Features
1160    // -------------------------------------------------------------------------
1161
1162    /// Stream all feature flags for this course.
1163    ///
1164    /// # Canvas API
1165    /// `GET /api/v1/courses/:course_id/features`
1166    pub fn get_features(&self) -> PageStream<Feature> {
1167        PageStream::new(
1168            Arc::clone(self.req()),
1169            &format!("courses/{}/features", self.id),
1170            vec![],
1171        )
1172    }
1173
1174    /// Fetch a specific feature flag for this course by feature name.
1175    ///
1176    /// # Canvas API
1177    /// `GET /api/v1/courses/:course_id/features/flags/:feature`
1178    pub async fn get_feature_flag(&self, feature: &str) -> Result<FeatureFlag> {
1179        let mut ff: FeatureFlag = self
1180            .req()
1181            .get(
1182                &format!("courses/{}/features/flags/{feature}", self.id),
1183                &[],
1184            )
1185            .await?;
1186        ff.requester = self.requester.clone();
1187        Ok(ff)
1188    }
1189
1190    /// List all enabled feature names for this course.
1191    ///
1192    /// # Canvas API
1193    /// `GET /api/v1/courses/:course_id/features/enabled`
1194    pub async fn get_enabled_features(&self) -> Result<Vec<String>> {
1195        self.req()
1196            .get(&format!("courses/{}/features/enabled", self.id), &[])
1197            .await
1198    }
1199
1200    // -------------------------------------------------------------------------
1201    // LTI Resource Links
1202    // -------------------------------------------------------------------------
1203
1204    /// Stream all LTI resource links in this course.
1205    ///
1206    /// # Canvas API
1207    /// `GET /api/v1/courses/:course_id/lti_resource_links`
1208    pub fn get_lti_resource_links(&self) -> PageStream<LtiResourceLink> {
1209        PageStream::new(
1210            Arc::clone(self.req()),
1211            &format!("courses/{}/lti_resource_links", self.id),
1212            vec![],
1213        )
1214    }
1215
1216    /// Fetch a single LTI resource link.
1217    ///
1218    /// # Canvas API
1219    /// `GET /api/v1/courses/:course_id/lti_resource_links/:id`
1220    pub async fn get_lti_resource_link(&self, link_id: u64) -> Result<LtiResourceLink> {
1221        self.req()
1222            .get(
1223                &format!("courses/{}/lti_resource_links/{link_id}", self.id),
1224                &[],
1225            )
1226            .await
1227    }
1228
1229    /// Create a new LTI resource link in this course.
1230    ///
1231    /// # Canvas API
1232    /// `POST /api/v1/courses/:course_id/lti_resource_links`
1233    pub async fn create_lti_resource_link(
1234        &self,
1235        params: CreateLtiResourceLinkParams,
1236    ) -> Result<LtiResourceLink> {
1237        let form = crate::params::flatten_params(&serde_json::to_value(&params).unwrap());
1238        self.req()
1239            .post(&format!("courses/{}/lti_resource_links", self.id), &form)
1240            .await
1241    }
1242
1243    /// Conclude (soft-delete) this course.
1244    ///
1245    /// # Canvas API
1246    /// `DELETE /api/v1/courses/:id?event=conclude`
1247    pub async fn conclude(&self) -> Result<serde_json::Value> {
1248        self.req()
1249            .delete(
1250                &format!("courses/{}", self.id),
1251                &[("event".to_string(), "conclude".to_string())],
1252            )
1253            .await
1254    }
1255
1256    /// Reset this course to a blank state (removes all content).
1257    ///
1258    /// # Canvas API
1259    /// `POST /api/v1/courses/:id/reset_content`
1260    pub async fn reset(&self) -> Result<Course> {
1261        let mut c: Course = self
1262            .req()
1263            .post(&format!("courses/{}/reset_content", self.id), &[])
1264            .await?;
1265        c.requester = Some(Arc::clone(self.req()));
1266        Ok(c)
1267    }
1268
1269    /// Get this course's settings.
1270    ///
1271    /// # Canvas API
1272    /// `GET /api/v1/courses/:id/settings`
1273    pub async fn get_settings(&self) -> Result<serde_json::Value> {
1274        self.req()
1275            .get(&format!("courses/{}/settings", self.id), &[])
1276            .await
1277    }
1278
1279    /// Update this course's settings.
1280    ///
1281    /// # Canvas API
1282    /// `PUT /api/v1/courses/:id/settings`
1283    pub async fn update_settings(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
1284        self.req()
1285            .put(&format!("courses/{}/settings", self.id), params)
1286            .await
1287    }
1288
1289    /// Get the late policy for this course.
1290    ///
1291    /// # Canvas API
1292    /// `GET /api/v1/courses/:id/late_policy`
1293    pub async fn get_late_policy(&self) -> Result<serde_json::Value> {
1294        self.req()
1295            .get(&format!("courses/{}/late_policy", self.id), &[])
1296            .await
1297    }
1298
1299    /// Stream multiple submissions for this course.
1300    ///
1301    /// # Canvas API
1302    /// `GET /api/v1/courses/:id/students/submissions`
1303    pub fn get_multiple_submissions(&self) -> PageStream<crate::resources::submission::Submission> {
1304        let course_id = self.id;
1305        PageStream::new_with_injector(
1306            Arc::clone(self.req()),
1307            &format!("courses/{}/students/submissions", self.id),
1308            vec![],
1309            move |mut s: crate::resources::submission::Submission, req| {
1310                s.requester = Some(Arc::clone(&req));
1311                s.course_id = Some(course_id);
1312                s
1313            },
1314        )
1315    }
1316
1317    /// Enroll a user in this course.
1318    ///
1319    /// # Canvas API
1320    /// `POST /api/v1/courses/:id/enrollments`
1321    pub async fn enroll_user(
1322        &self,
1323        user_id: u64,
1324        enrollment_type: &str,
1325    ) -> Result<crate::resources::enrollment::Enrollment> {
1326        let params = vec![
1327            ("enrollment[user_id]".to_string(), user_id.to_string()),
1328            ("enrollment[type]".to_string(), enrollment_type.to_string()),
1329        ];
1330        let mut e: crate::resources::enrollment::Enrollment = self
1331            .req()
1332            .post(&format!("courses/{}/enrollments", self.id), &params)
1333            .await?;
1334        e.requester = Some(Arc::clone(self.req()));
1335        Ok(e)
1336    }
1337
1338    /// Bulk-update grades for this course asynchronously.
1339    ///
1340    /// # Canvas API
1341    /// `POST /api/v1/courses/:id/submissions/update_grades`
1342    pub async fn submissions_bulk_update(
1343        &self,
1344        params: &[(String, String)],
1345    ) -> Result<crate::resources::progress::Progress> {
1346        let mut p: crate::resources::progress::Progress = self
1347            .req()
1348            .post(
1349                &format!("courses/{}/submissions/update_grades", self.id),
1350                params,
1351            )
1352            .await?;
1353        p.requester = Some(Arc::clone(self.req()));
1354        Ok(p)
1355    }
1356
1357    // -------------------------------------------------------------------------
1358    // Front page
1359    // -------------------------------------------------------------------------
1360
1361    /// Fetch the front page of this course.
1362    ///
1363    /// # Canvas API
1364    /// `GET /api/v1/courses/:id/front_page`
1365    pub async fn show_front_page(&self) -> Result<Page> {
1366        let mut p: Page = self
1367            .req()
1368            .get(&format!("courses/{}/front_page", self.id), &[])
1369            .await?;
1370        p.course_id = Some(self.id);
1371        p.requester = self.requester.clone();
1372        Ok(p)
1373    }
1374
1375    /// Update the front page of this course.
1376    ///
1377    /// # Canvas API
1378    /// `PUT /api/v1/courses/:id/front_page`
1379    pub async fn edit_front_page(&self, params: &[(String, String)]) -> Result<Page> {
1380        let mut p: Page = self
1381            .req()
1382            .put(&format!("courses/{}/front_page", self.id), params)
1383            .await?;
1384        p.course_id = Some(self.id);
1385        p.requester = self.requester.clone();
1386        Ok(p)
1387    }
1388
1389    // -------------------------------------------------------------------------
1390    // Content
1391    // -------------------------------------------------------------------------
1392
1393    /// Export course content (shorthand for create_content_export).
1394    ///
1395    /// # Canvas API
1396    /// `POST /api/v1/courses/:id/content_exports`
1397    pub async fn export_content(&self, export_type: &str) -> Result<ContentExport> {
1398        let params = vec![("export_type".to_string(), export_type.to_string())];
1399        self.req()
1400            .post(&format!("courses/{}/content_exports", self.id), &params)
1401            .await
1402    }
1403
1404    /// Fetch the full view of a discussion topic (including entries).
1405    ///
1406    /// # Canvas API
1407    /// `GET /api/v1/courses/:id/discussion_topics/:topic_id/view`
1408    pub async fn get_full_discussion_topic(&self, topic_id: u64) -> Result<serde_json::Value> {
1409        self.req()
1410            .get(
1411                &format!("courses/{}/discussion_topics/{topic_id}/view", self.id),
1412                &[],
1413            )
1414            .await
1415    }
1416
1417    /// Convert HTML to the format Canvas uses for this course (sanitizes and processes).
1418    ///
1419    /// # Canvas API
1420    /// `POST /api/v1/courses/:id/preview_html`
1421    pub async fn preview_html(&self, html: &str) -> Result<String> {
1422        let params = vec![("html".to_string(), html.to_string())];
1423        let val: serde_json::Value = self
1424            .req()
1425            .post(&format!("courses/{}/preview_html", self.id), &params)
1426            .await?;
1427        Ok(val
1428            .get("html")
1429            .and_then(|v| v.as_str())
1430            .unwrap_or("")
1431            .to_string())
1432    }
1433
1434    /// Reorder pinned discussion topics for this course.
1435    ///
1436    /// # Canvas API
1437    /// `POST /api/v1/courses/:id/discussion_topics/reorder`
1438    pub async fn reorder_pinned_topics(&self, order: &[u64]) -> Result<serde_json::Value> {
1439        let order_str = order
1440            .iter()
1441            .map(|id| id.to_string())
1442            .collect::<Vec<_>>()
1443            .join(",");
1444        let params = vec![("order".to_string(), order_str)];
1445        self.req()
1446            .post(
1447                &format!("courses/{}/discussion_topics/reorder", self.id),
1448                &params,
1449            )
1450            .await
1451    }
1452
1453    // -------------------------------------------------------------------------
1454    // Users (single)
1455    // -------------------------------------------------------------------------
1456
1457    /// Fetch a single user enrolled in this course.
1458    ///
1459    /// # Canvas API
1460    /// `GET /api/v1/courses/:id/users/:user_id`
1461    pub async fn get_user(&self, user_id: u64) -> Result<User> {
1462        let mut u: User = self
1463            .req()
1464            .get(&format!("courses/{}/users/{user_id}", self.id), &[])
1465            .await?;
1466        u.requester = self.requester.clone();
1467        Ok(u)
1468    }
1469
1470    /// Stream recently active students in this course.
1471    ///
1472    /// # Canvas API
1473    /// `GET /api/v1/courses/:id/recent_students`
1474    pub fn get_recent_students(&self) -> PageStream<User> {
1475        let req = Arc::clone(self.req());
1476        PageStream::new_with_injector(
1477            req,
1478            &format!("courses/{}/recent_students", self.id),
1479            vec![],
1480            |mut u: User, r| {
1481                u.requester = Some(Arc::clone(&r));
1482                u
1483            },
1484        )
1485    }
1486
1487    // -------------------------------------------------------------------------
1488    // Usage rights / licenses
1489    // -------------------------------------------------------------------------
1490
1491    /// Set usage rights on files within this course.
1492    ///
1493    /// # Canvas API
1494    /// `PUT /api/v1/courses/:id/usage_rights`
1495    pub async fn set_usage_rights(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
1496        self.req()
1497            .put(&format!("courses/{}/usage_rights", self.id), params)
1498            .await
1499    }
1500
1501    /// Remove usage rights from files within this course.
1502    ///
1503    /// # Canvas API
1504    /// `DELETE /api/v1/courses/:id/usage_rights`
1505    pub async fn remove_usage_rights(
1506        &self,
1507        params: &[(String, String)],
1508    ) -> Result<serde_json::Value> {
1509        self.req()
1510            .delete(&format!("courses/{}/usage_rights", self.id), params)
1511            .await
1512    }
1513
1514    /// Stream available content licenses for this course.
1515    ///
1516    /// # Canvas API
1517    /// `GET /api/v1/courses/:id/content_licenses`
1518    pub fn get_licenses(&self) -> PageStream<serde_json::Value> {
1519        PageStream::new(
1520            Arc::clone(self.req()),
1521            &format!("courses/{}/content_licenses", self.id),
1522            vec![],
1523        )
1524    }
1525
1526    // -------------------------------------------------------------------------
1527    // External feeds
1528    // -------------------------------------------------------------------------
1529
1530    /// Stream external feeds for this course.
1531    ///
1532    /// # Canvas API
1533    /// `GET /api/v1/courses/:id/external_feeds`
1534    pub fn get_external_feeds(&self) -> PageStream<serde_json::Value> {
1535        PageStream::new(
1536            Arc::clone(self.req()),
1537            &format!("courses/{}/external_feeds", self.id),
1538            vec![],
1539        )
1540    }
1541
1542    /// Create an external feed for this course.
1543    ///
1544    /// # Canvas API
1545    /// `POST /api/v1/courses/:id/external_feeds`
1546    pub async fn create_external_feed(&self, url: &str) -> Result<serde_json::Value> {
1547        let params = vec![("url".to_string(), url.to_string())];
1548        self.req()
1549            .post(&format!("courses/{}/external_feeds", self.id), &params)
1550            .await
1551    }
1552
1553    /// Delete an external feed from this course.
1554    ///
1555    /// # Canvas API
1556    /// `DELETE /api/v1/courses/:id/external_feeds/:feed_id`
1557    pub async fn delete_external_feed(&self, feed_id: u64) -> Result<serde_json::Value> {
1558        self.req()
1559            .delete(
1560                &format!("courses/{}/external_feeds/{feed_id}", self.id),
1561                &[],
1562            )
1563            .await
1564    }
1565
1566    // -------------------------------------------------------------------------
1567    // Sections
1568    // -------------------------------------------------------------------------
1569
1570    /// Create a section in this course.
1571    ///
1572    /// # Canvas API
1573    /// `POST /api/v1/courses/:id/sections`
1574    pub async fn create_course_section(&self, params: &[(String, String)]) -> Result<Section> {
1575        let mut s: Section = self
1576            .req()
1577            .post(&format!("courses/{}/sections", self.id), params)
1578            .await?;
1579        s.course_id = Some(self.id);
1580        s.requester = self.requester.clone();
1581        Ok(s)
1582    }
1583
1584    // -------------------------------------------------------------------------
1585    // Outcome imports
1586    // -------------------------------------------------------------------------
1587
1588    /// Import outcomes into this course.
1589    ///
1590    /// # Canvas API
1591    /// `POST /api/v1/courses/:id/outcome_imports`
1592    pub async fn import_outcomes(&self, params: &[(String, String)]) -> Result<OutcomeImport> {
1593        let context = format!("courses/{}", self.id);
1594        let mut import: OutcomeImport = self
1595            .req()
1596            .post(&format!("{}/outcome_imports", context), params)
1597            .await?;
1598        import.requester = self.requester.clone();
1599        import.course_id = Some(self.id);
1600        import.context_path = Some(context);
1601        Ok(import)
1602    }
1603
1604    /// Get the status of an outcome import for this course.
1605    ///
1606    /// # Canvas API
1607    /// `GET /api/v1/courses/:id/outcome_imports/:id`
1608    pub async fn get_outcome_import_status(&self, import_id: u64) -> Result<OutcomeImport> {
1609        let context = format!("courses/{}", self.id);
1610        let mut import: OutcomeImport = self
1611            .req()
1612            .get(&format!("{}/outcome_imports/{import_id}", context), &[])
1613            .await?;
1614        import.requester = self.requester.clone();
1615        import.course_id = Some(self.id);
1616        import.context_path = Some(context);
1617        Ok(import)
1618    }
1619}