diff --git a/apps/labrinth/src/routes/internal/moderation/mod.rs b/apps/labrinth/src/routes/internal/moderation/mod.rs index 3a8ce7faa8..7e620fba21 100644 --- a/apps/labrinth/src/routes/internal/moderation/mod.rs +++ b/apps/labrinth/src/routes/internal/moderation/mod.rs @@ -1680,22 +1680,23 @@ pub async fn delete_all_locks( Ok(web::Json(DeleteAllLocksResponse { deleted_count })) } -/// Get counts of each `ProjectStatus` across a user's projects. Only -/// statuses with at least one project are present in the map. +/// Get project id's for a given user with them grouped by their `ProjectStatus`. +/// +/// Only statuses with at least one project are present in the map. #[utoipa::path( context_path = "/moderation", tag = "moderation", security(("bearer_auth" = [])), - responses((status = OK, body = HashMap)) + responses((status = OK, body = HashMap>)) )] -#[get("/user/{user_id}/project-status-counts")] -pub async fn get_user_project_status_counts( +#[get("/user/{user_id}/all-projects-grouped")] +pub async fn get_user_project_grouped( req: HttpRequest, info: web::Path<(String,)>, pool: web::Data, redis: web::Data, session_queue: web::Data, -) -> Result>, ApiError> { +) -> Result>>, ApiError> { check_is_moderator_from_headers( &req, &**pool, @@ -1713,17 +1714,13 @@ pub async fn get_user_project_status_counts( .wrap_not_found_err("resource not found")?; let counts = - user_project_status_counts(target_user.id, &**pool, &redis).await?; + user_projects_status_grouped(target_user.id, &**pool, &redis).await?; Ok(web::Json(counts)) } -#[derive(Debug, Deserialize)] -pub struct ProjectStatusCountsUserIds { - pub ids: String, -} - -/// Get counts of each `ProjectStatus` across multiple users' projects. +/// Get project id's for a list of user's with them grouped by their `ProjectStatus`. +/// /// Users that don't exist are silently omitted from the response; users /// that exist but have no projects are included with an empty map. #[utoipa::path( @@ -1731,16 +1728,16 @@ pub struct ProjectStatusCountsUserIds { tag = "moderation", security(("bearer_auth" = [])), params(("ids" = String, Query)), - responses((status = OK, body = HashMap>)) + responses((status = OK, body = HashMap>>)) )] -#[get("/users/project-status-counts")] -pub async fn get_users_project_status_counts( +#[get("/users/all-projects-grouped")] +pub async fn get_users_project_grouped( req: HttpRequest, - ids: web::Query, + ids: web::Query, pool: web::Data, redis: web::Data, session_queue: web::Data, -) -> Result>>, ApiError> +) -> Result>>>, ApiError> { check_is_moderator_from_headers( &req, @@ -1767,9 +1764,9 @@ pub async fn get_users_project_status_counts( let pool_ref = &**pool; let redis_ref = &*redis; - let counts_by_user = try_join_all(target_users.into_iter().map( + let grouped_projects_by_user = try_join_all(target_users.into_iter().map( |target_user| async move { - let counts = user_project_status_counts( + let counts = user_projects_status_grouped( target_user.id, pool_ref, redis_ref, @@ -1783,25 +1780,26 @@ pub async fn get_users_project_status_counts( .into_iter() .collect::>(); - Ok(web::Json(counts_by_user)) + Ok(web::Json(grouped_projects_by_user)) } -/// Get counts of each `ProjectStatus` across an organization's projects. +/// Get project id's for a given organization with them grouped by their `ProjectStatus`. +/// /// Only statuses with at least one project are present in the map. #[utoipa::path( context_path = "/moderation", tag = "moderation", security(("bearer_auth" = [])), - responses((status = OK, body = HashMap)) + responses((status = OK, body = HashMap>)) )] -#[get("/organization/{organization_id}/project-status-counts")] -pub async fn get_organization_project_status_counts( +#[get("/organization/{organization_id}/all-projects-grouped")] +pub async fn get_organization_project_grouped( req: HttpRequest, info: web::Path<(String,)>, pool: web::Data, redis: web::Data, session_queue: web::Data, -) -> Result>, ApiError> { +) -> Result>>, ApiError> { check_is_moderator_from_headers( &req, &**pool, @@ -1821,18 +1819,19 @@ pub async fn get_organization_project_status_counts( .wrap_internal_err("fetching organization from database")? .wrap_not_found_err("resource not found")?; - let counts = organization_project_status_counts( + let grouped_projects = organization_projects_status_grouped( target_org.id, &**pool, &redis, ) .await?; - Ok(web::Json(counts)) + Ok(web::Json(grouped_projects)) } -/// Get counts of each `ProjectStatus` across multiple organizations' -/// projects. Organizations that don't exist are silently omitted from the +/// Get project id's for a list of organization's with them grouped by their `ProjectStatus`. +/// +/// Organizations that don't exist are silently omitted from the /// response; organizations that exist but have no projects are included /// with an empty map. #[utoipa::path( @@ -1840,17 +1839,17 @@ pub async fn get_organization_project_status_counts( tag = "moderation", security(("bearer_auth" = [])), params(("ids" = String, Query)), - responses((status = OK, body = HashMap>)) + responses((status = OK, body = HashMap>>)) )] -#[get("/organizations/project-status-counts")] -pub async fn get_organizations_project_status_counts( +#[get("/organizations/all-projects-grouped")] +pub async fn get_organizations_project_grouped( req: HttpRequest, ids: web::Query, pool: web::Data, redis: web::Data, session_queue: web::Data, ) -> Result< - web::Json>>, + web::Json>>>, ApiError, > { check_is_moderator_from_headers( @@ -1881,9 +1880,9 @@ pub async fn get_organizations_project_status_counts( let pool_ref = &**pool; let redis_ref = &*redis; - let counts_by_org = try_join_all(target_orgs.into_iter().map( + let grouped_projects_by_org = try_join_all(target_orgs.into_iter().map( |target_org| async move { - let counts = organization_project_status_counts( + let counts = organization_projects_status_grouped( target_org.id, pool_ref, redis_ref, @@ -1897,37 +1896,35 @@ pub async fn get_organizations_project_status_counts( .into_iter() .collect::>(); - Ok(web::Json(counts_by_org)) + Ok(web::Json(grouped_projects_by_org)) } -/// Tallies a single user's projects by `ProjectStatus`. Shared by the -/// single- and multi-user project-status-count endpoints. -async fn user_project_status_counts<'a, E>( +/// Groups the given User projects by their `ProjectStatus`. +async fn user_projects_status_grouped<'a, E>( user_id: database::models::DBUserId, pool: E, redis: &RedisPool, -) -> Result, ApiError> +) -> Result>, ApiError> where E: database::Executor<'a, Database = sqlx::Postgres> + database::Acquire<'a, Database = sqlx::Postgres> + Copy, { let project_ids = - models::DBUser::get_projects(user_id, pool, redis) + database::models::DBUser::get_projects(user_id, pool, redis) .await .wrap_internal_err("fetching user's projects from database")?; - project_status_counts_for(&project_ids, pool, redis).await + grouped_projects_for(&project_ids, pool, redis).await } -// Tallies a single organization's projects by `ProjectStatus`. Shared by -/// the single- and multi-organization project-status-count endpoints. -async fn organization_project_status_counts<'a, E>( +/// Groups the given Organization projects by their `ProjectStatus`. +async fn organization_projects_status_grouped<'a, E>( organization_id: DBOrganizationId, pool: E, redis: &RedisPool, -) -> Result, ApiError> +) -> Result>, ApiError> where E: database::Executor<'a, Database = sqlx::Postgres> + database::Acquire<'a, Database = sqlx::Postgres> @@ -1937,16 +1934,15 @@ where .await .wrap_internal_err("fetching project IDs from database")?; - project_status_counts_for(&project_ids, pool, redis).await + grouped_projects_for(&project_ids, pool, redis).await } -/// Tallies `ProjectStatus` counts across the given projects. Shared by the -/// user- and organization-scoped project-status-count helpers above. -async fn project_status_counts_for<'a, E>( +/// Groups the given input Projects by their `ProjectStatus`. +async fn grouped_projects_for<'a, E>( project_ids: &[DBProjectId], pool: E, redis: &RedisPool, -) -> Result, ApiError> +) -> Result>, ApiError> where E: database::Executor<'a, Database = sqlx::Postgres> + database::Acquire<'a, Database = sqlx::Postgres> @@ -1961,10 +1957,10 @@ where .await .wrap_internal_err("fetching projects from database")?; - let mut counts = HashMap::new(); + let mut grouped_projects = HashMap::new(); for project in &projects { - *counts.entry(project.inner.status).or_insert(0u32) += 1; + *grouped_projects.entry(project.inner.status).or_default().push(project.inner.id.into()); } - Ok(counts) + Ok(grouped_projects) }