use crate::{
error::{CanvasError, Result},
http::Requester,
pagination::PageStream,
params::wrap_params,
resources::{
collaboration::Collaboration, discussion_topic::DiscussionTopic, file::File,
folder::Folder, page::Page, progress::Progress, user::User,
},
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Default, Clone, Serialize)]
pub struct UpdateGroupParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_public: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub join_level: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_quota_mb: Option<u64>,
}
#[derive(Debug, Default, Clone, Serialize)]
pub struct UpdateMembershipParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub workflow_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub moderator: Option<bool>,
}
#[derive(Debug, Default, Clone, Serialize)]
pub struct GroupCategoryParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub self_signup: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_leader: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_group_count: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
pub struct Group {
pub id: u64,
pub name: Option<String>,
pub description: Option<String>,
pub is_public: Option<bool>,
pub followed_by_user: Option<bool>,
pub join_level: Option<String>,
pub members_count: Option<u64>,
pub avatar_url: Option<String>,
pub course_id: Option<u64>,
pub role: Option<String>,
pub group_category_id: Option<u64>,
pub sis_group_id: Option<String>,
pub sis_import_id: Option<u64>,
pub storage_quota_mb: Option<u64>,
pub permissions: Option<serde_json::Value>,
#[serde(skip)]
pub(crate) requester: Option<Arc<Requester>>,
}
impl Group {
fn propagate(&self, g: &mut Group) {
g.requester = self.requester.clone();
}
pub async fn edit(&self, params: UpdateGroupParams) -> Result<Group> {
let form = wrap_params("group", ¶ms);
let mut g: Group = self
.req()
.put(&format!("groups/{}", self.id), &form)
.await?;
self.propagate(&mut g);
Ok(g)
}
pub async fn delete(&self) -> Result<()> {
self.req().delete_void(&format!("groups/{}", self.id)).await
}
pub fn get_users(&self) -> PageStream<User> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/users", self.id),
vec![],
)
}
pub fn get_memberships(&self) -> PageStream<GroupMembership> {
let group_id = self.id;
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{}/memberships", self.id),
vec![],
move |mut m: GroupMembership, req| {
m.requester = Some(Arc::clone(&req));
m.group_id = Some(group_id);
m
},
)
}
pub async fn create_membership(&self, user_id: u64) -> Result<GroupMembership> {
let params = vec![("user_id".to_string(), user_id.to_string())];
let mut m: GroupMembership = self
.req()
.post(&format!("groups/{}/memberships", self.id), ¶ms)
.await?;
m.requester = self.requester.clone();
m.group_id = Some(self.id);
Ok(m)
}
pub async fn get_membership(&self, user_id: u64) -> Result<GroupMembership> {
let mut m: GroupMembership = self
.req()
.get(
&format!("groups/{}/users/{user_id}/membership", self.id),
&[],
)
.await?;
m.requester = self.requester.clone();
m.group_id = Some(self.id);
Ok(m)
}
pub async fn update_membership(
&self,
membership_id: u64,
params: UpdateMembershipParams,
) -> Result<GroupMembership> {
let form = wrap_params("membership", ¶ms);
let mut m: GroupMembership = self
.req()
.put(
&format!("groups/{}/memberships/{membership_id}", self.id),
&form,
)
.await?;
m.requester = self.requester.clone();
m.group_id = Some(self.id);
Ok(m)
}
pub async fn remove_user(&self, user_id: u64) -> Result<()> {
self.req()
.delete_void(&format!("groups/{}/users/{user_id}", self.id))
.await
}
pub async fn invite(&self, invitee_ids: &[u64]) -> Result<Vec<GroupMembership>> {
let params: Vec<(String, String)> = invitee_ids
.iter()
.map(|id| ("invitees[]".to_string(), id.to_string()))
.collect();
let memberships: Vec<GroupMembership> = self
.req()
.post(&format!("groups/{}/invite", self.id), ¶ms)
.await?;
let group_id = self.id;
Ok(memberships
.into_iter()
.map(|mut m| {
m.requester = self.requester.clone();
m.group_id = Some(group_id);
m
})
.collect())
}
pub fn get_files(&self) -> PageStream<File> {
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{}/files", self.id),
vec![],
|mut f: File, req| {
f.requester = Some(Arc::clone(&req));
f
},
)
}
pub async fn get_file(&self, file_id: u64) -> Result<File> {
let mut f: File = self
.req()
.get(&format!("groups/{}/files/{file_id}", self.id), &[])
.await?;
f.requester = self.requester.clone();
Ok(f)
}
pub fn get_folders(&self) -> PageStream<Folder> {
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{}/folders", self.id),
vec![],
|mut f: Folder, req| {
f.requester = Some(Arc::clone(&req));
f
},
)
}
pub async fn get_folder(&self, folder_id: u64) -> Result<Folder> {
let mut f: Folder = self
.req()
.get(&format!("groups/{}/folders/{folder_id}", self.id), &[])
.await?;
f.requester = self.requester.clone();
Ok(f)
}
pub async fn create_folder(&self, name: &str) -> Result<Folder> {
let params = vec![("name".to_string(), name.to_string())];
let mut f: Folder = self
.req()
.post(&format!("groups/{}/folders", self.id), ¶ms)
.await?;
f.requester = self.requester.clone();
Ok(f)
}
pub fn get_pages(&self) -> PageStream<Page> {
let group_id = self.id;
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{}/pages", self.id),
vec![],
move |mut p: Page, req| {
p.requester = Some(Arc::clone(&req));
p.group_id = Some(group_id);
p
},
)
}
pub async fn get_page(&self, url_slug: &str) -> Result<Page> {
let mut p: Page = self
.req()
.get(&format!("groups/{}/pages/{url_slug}", self.id), &[])
.await?;
p.requester = self.requester.clone();
p.group_id = Some(self.id);
Ok(p)
}
pub fn get_discussion_topics(&self) -> PageStream<DiscussionTopic> {
let group_id = self.id;
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{}/discussion_topics", self.id),
vec![],
move |mut t: DiscussionTopic, req| {
t.requester = Some(Arc::clone(&req));
t.group_id = Some(group_id);
t
},
)
}
pub async fn get_discussion_topic(&self, topic_id: u64) -> Result<DiscussionTopic> {
let mut t: DiscussionTopic = self
.req()
.get(
&format!("groups/{}/discussion_topics/{topic_id}", self.id),
&[],
)
.await?;
t.requester = self.requester.clone();
t.group_id = Some(self.id);
Ok(t)
}
pub async fn create_page(&self, params: &[(String, String)]) -> Result<Page> {
let mut p: Page = self
.req()
.post(&format!("groups/{}/pages", self.id), params)
.await?;
p.requester = self.requester.clone();
p.group_id = Some(self.id);
Ok(p)
}
pub async fn create_discussion_topic(
&self,
params: &[(String, String)],
) -> Result<DiscussionTopic> {
let group_id = self.id;
let mut t: DiscussionTopic = self
.req()
.post(&format!("groups/{}/discussion_topics", self.id), params)
.await?;
t.requester = self.requester.clone();
t.group_id = Some(group_id);
Ok(t)
}
pub fn get_tabs(&self) -> PageStream<serde_json::Value> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/tabs", self.id),
vec![],
)
}
pub fn get_content_migrations(&self) -> PageStream<serde_json::Value> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/content_migrations", self.id),
vec![],
)
}
pub fn get_content_exports(&self) -> PageStream<serde_json::Value> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/content_exports", self.id),
vec![],
)
}
pub async fn preview_html(&self, html: &str) -> Result<serde_json::Value> {
let params = vec![("html".to_string(), html.to_string())];
self.req()
.post(&format!("groups/{}/preview_html", self.id), ¶ms)
.await
}
pub fn resolve_path(&self, full_path: Option<&str>) -> PageStream<Folder> {
let path = match full_path {
Some(p) if !p.is_empty() => format!("groups/{}/folders/by_path/{p}", self.id),
_ => format!("groups/{}/folders/by_path", self.id),
};
PageStream::new(Arc::clone(self.req()), &path, vec![])
}
pub fn get_collaborations(&self) -> PageStream<Collaboration> {
let group_id = self.id;
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("groups/{group_id}/collaborations"),
vec![],
{
let req = Arc::clone(self.req());
move |mut c: Collaboration, _| {
c.requester = Some(Arc::clone(&req));
c
}
},
)
}
pub async fn show_front_page(&self) -> Result<Page> {
let mut p: Page = self
.req()
.get(&format!("groups/{}/front_page", self.id), &[])
.await?;
p.group_id = Some(self.id);
p.requester = self.requester.clone();
Ok(p)
}
pub async fn edit_front_page(&self, params: &[(String, String)]) -> Result<Page> {
let mut p: Page = self
.req()
.put(&format!("groups/{}/front_page", self.id), params)
.await?;
p.group_id = Some(self.id);
p.requester = self.requester.clone();
Ok(p)
}
pub async fn get_file_quota(&self) -> Result<serde_json::Value> {
self.req()
.get(&format!("groups/{}/files/quota", self.id), &[])
.await
}
pub fn get_external_feeds(&self) -> PageStream<serde_json::Value> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/external_feeds", self.id),
vec![],
)
}
pub async fn create_external_feed(&self, url: &str) -> Result<serde_json::Value> {
let params = vec![("url".to_string(), url.to_string())];
self.req()
.post(&format!("groups/{}/external_feeds", self.id), ¶ms)
.await
}
pub async fn delete_external_feed(&self, feed_id: u64) -> Result<serde_json::Value> {
self.req()
.delete(&format!("groups/{}/external_feeds/{feed_id}", self.id), &[])
.await
}
pub async fn get_assignment_override(&self, assignment_id: u64) -> Result<serde_json::Value> {
self.req()
.get(
&format!("groups/{}/assignments/{assignment_id}/override", self.id),
&[],
)
.await
}
pub async fn set_usage_rights(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
self.req()
.put(&format!("groups/{}/usage_rights", self.id), params)
.await
}
pub async fn remove_usage_rights(
&self,
params: &[(String, String)],
) -> Result<serde_json::Value> {
self.req()
.delete(&format!("groups/{}/usage_rights", self.id), params)
.await
}
pub fn get_licenses(&self) -> PageStream<serde_json::Value> {
PageStream::new(
Arc::clone(self.req()),
&format!("groups/{}/content_licenses", self.id),
vec![],
)
}
pub async fn upload_file(
&self,
request: crate::upload::UploadRequest,
data: Vec<u8>,
) -> Result<File> {
crate::upload::initiate_and_upload(
self.req(),
&format!("groups/{}/files", self.id),
request,
data,
)
.await
}
}
#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
pub struct GroupMembership {
pub id: u64,
pub group_id: Option<u64>,
pub user_id: Option<u64>,
pub workflow_state: Option<String>,
pub moderator: Option<bool>,
pub just_created: Option<bool>,
#[serde(skip)]
pub(crate) requester: Option<Arc<Requester>>,
}
impl GroupMembership {
fn group_id_or_err(&self) -> Result<u64> {
self.group_id.ok_or_else(|| CanvasError::BadRequest {
message: "GroupMembership has no group_id".to_string(),
errors: vec![],
})
}
pub async fn update(&self, params: UpdateMembershipParams) -> Result<GroupMembership> {
let group_id = self.group_id_or_err()?;
let form = wrap_params("membership", ¶ms);
let mut m: GroupMembership = self
.req()
.put(&format!("groups/{group_id}/memberships/{}", self.id), &form)
.await?;
m.requester = self.requester.clone();
m.group_id = self.group_id;
Ok(m)
}
pub async fn remove_self(&self) -> Result<()> {
let group_id = self.group_id_or_err()?;
self.req()
.delete_void(&format!("groups/{group_id}/memberships/{}", self.id))
.await
}
}
#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
pub struct GroupCategory {
pub id: u64,
pub name: Option<String>,
pub role: Option<String>,
pub self_signup: Option<String>,
pub auto_leader: Option<String>,
pub context_type: Option<String>,
pub context_id: Option<u64>,
pub group_limit: Option<u64>,
pub groups_count: Option<u64>,
pub unassigned_users_count: Option<u64>,
#[serde(skip)]
pub(crate) requester: Option<Arc<Requester>>,
}
impl GroupCategory {
pub async fn update(&self, params: GroupCategoryParams) -> Result<GroupCategory> {
let form = wrap_params("group_category", ¶ms);
let mut gc: GroupCategory = self
.req()
.put(&format!("group_categories/{}", self.id), &form)
.await?;
gc.requester = self.requester.clone();
Ok(gc)
}
pub async fn delete(&self) -> Result<()> {
self.req()
.delete_void(&format!("group_categories/{}", self.id))
.await
}
pub fn get_groups(&self) -> PageStream<Group> {
PageStream::new_with_injector(
Arc::clone(self.req()),
&format!("group_categories/{}/groups", self.id),
vec![],
|mut g: Group, req| {
g.requester = Some(Arc::clone(&req));
g
},
)
}
pub fn get_users(&self) -> PageStream<User> {
PageStream::new(
Arc::clone(self.req()),
&format!("group_categories/{}/users", self.id),
vec![],
)
}
pub async fn create_group(&self, name: &str) -> Result<Group> {
let params = vec![("name".to_string(), name.to_string())];
let mut g: Group = self
.req()
.post(&format!("group_categories/{}/groups", self.id), ¶ms)
.await?;
g.requester = self.requester.clone();
Ok(g)
}
pub async fn assign_members(&self) -> Result<Progress> {
let mut p: Progress = self
.req()
.post(
&format!("group_categories/{}/assign_unassigned_members", self.id),
&[],
)
.await?;
p.requester = self.requester.clone();
Ok(p)
}
}