use crate::api::{SingleResponse, api_delete, api_post, api_put};
use crate::{
api::{ApiClient, ApiError},
models::{
MemberPermission, Project, ProjectCreateRequest, ProjectListParams, ProjectMember,
ProjectMembers, ProjectSort, ProjectStatus, ProjectUpdateRequest,
},
};
use futures_util::{StreamExt, pin_mut};
use serde::Serialize;
pub async fn list_projects(
client: &ApiClient,
mut params: ProjectListParams,
) -> Result<Vec<Project>, ApiError> {
ensure_default_fields(&mut params);
let query = params.to_query();
let max_items = params.limit;
let stream = client.paginate_with_limit::<Project>("/projects", query, max_items);
pin_mut!(stream);
let mut projects = Vec::new();
while let Some(page) = stream.next().await {
let mut page = page?;
projects.append(&mut page);
}
if !params.filters.is_empty() {
projects.retain(|project| project.matches(¶ms.filters));
}
if let Some(sort) = params.sort {
sort_projects(&mut projects, sort);
}
Ok(projects)
}
pub async fn get_project(
client: &ApiClient,
gid: &str,
fields: Vec<String>,
) -> Result<Project, ApiError> {
let mut query = Vec::new();
if !fields.is_empty() {
query.push(("opt_fields".into(), fields.join(",")));
}
let response: SingleResponse<Project> = client
.get_json_with_pairs(&format!("/projects/{gid}"), query)
.await?;
Ok(response.data)
}
pub async fn create_project(
client: &ApiClient,
request: ProjectCreateRequest,
) -> Result<Project, ApiError> {
api_post(client, "/projects", &request).await
}
pub async fn update_project(
client: &ApiClient,
gid: &str,
request: ProjectUpdateRequest,
) -> Result<Project, ApiError> {
api_put(client, &format!("/projects/{gid}"), &request).await
}
pub async fn delete_project(client: &ApiClient, gid: &str) -> Result<(), ApiError> {
api_delete(client, &format!("/projects/{gid}")).await
}
pub async fn list_members(client: &ApiClient, gid: &str) -> Result<ProjectMembers, ApiError> {
let response: SingleResponse<Vec<ProjectMember>> = client
.get_json_with_pairs(&format!("/projects/{gid}/members"), vec![])
.await?;
Ok(ProjectMembers {
project_gid: gid.to_string(),
members: response.data,
})
}
pub async fn list_statuses(
client: &ApiClient,
gid: &str,
limit: Option<usize>,
) -> Result<Vec<ProjectStatus>, ApiError> {
let response: SingleResponse<Vec<ProjectStatus>> = client
.get_json_with_pairs(&format!("/projects/{gid}/project_statuses"), vec![])
.await?;
let mut statuses = response.data;
if let Some(limit) = limit
&& statuses.len() > limit
{
statuses.truncate(limit);
}
Ok(statuses)
}
pub async fn add_members(
client: &ApiClient,
gid: &str,
members: Vec<String>,
role: Option<MemberPermission>,
) -> Result<(), ApiError> {
let payload = ModifyMembersRequest {
data: ModifyMembersData { members, role },
};
client
.post_void(&format!("/projects/{gid}/addMembers"), &payload)
.await
}
pub async fn remove_members(
client: &ApiClient,
gid: &str,
members: Vec<String>,
) -> Result<(), ApiError> {
let payload = ModifyMembersRequest {
data: ModifyMembersData {
members,
role: None,
},
};
client
.post_void(&format!("/projects/{gid}/removeMembers"), &payload)
.await
}
pub async fn update_member(
client: &ApiClient,
membership_gid: &str,
role: MemberPermission,
) -> Result<ProjectMember, ApiError> {
let payload = UpdateMemberRequest {
data: MemberRoleUpdate { role },
};
let response: SingleResponse<ProjectMember> = client
.put_json(&format!("/project_memberships/{membership_gid}"), &payload)
.await?;
Ok(response.data)
}
fn ensure_default_fields(params: &mut ProjectListParams) {
let defaults = [
"gid",
"name",
"archived",
"color",
"created_at",
"modified_at",
"workspace.name",
"workspace.gid",
"team.name",
"team.gid",
"owner.name",
"owner.gid",
"due_on",
"start_on",
];
for field in defaults {
params.fields.insert(field.to_string());
}
}
fn sort_projects(projects: &mut [Project], sort: ProjectSort) {
match sort {
ProjectSort::Name => projects.sort_by(|a, b| {
a.name
.to_ascii_lowercase()
.cmp(&b.name.to_ascii_lowercase())
}),
ProjectSort::CreatedAt => projects.sort_by(|a, b| a.created_at.cmp(&b.created_at)),
ProjectSort::ModifiedAt => projects.sort_by(|a, b| a.modified_at.cmp(&b.modified_at)),
}
}
#[derive(Debug, Serialize)]
struct ModifyMembersRequest {
data: ModifyMembersData,
}
#[derive(Debug, Serialize)]
struct ModifyMembersData {
members: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
role: Option<MemberPermission>,
}
#[derive(Debug, Serialize)]
struct UpdateMemberRequest {
data: MemberRoleUpdate,
}
#[derive(Debug, Serialize)]
struct MemberRoleUpdate {
role: MemberPermission,
}