From b8982a6d17eb1408b51eeb19e9216120f04908ec Mon Sep 17 00:00:00 2001 From: Emma Alexia Date: Sun, 27 Jul 2025 14:23:49 -0400 Subject: [PATCH] Hopefully fix collection visibility once and for all (#4070) * Hopefully fix collection visibility once and for all Follow up to #3408 and #3864 * Use same unlisted approach for collections as is used for projects --- apps/labrinth/src/auth/checks.rs | 15 ++++++++-- apps/labrinth/src/models/v3/collections.rs | 7 ++++- apps/labrinth/src/routes/v3/collections.rs | 5 ++-- apps/labrinth/src/routes/v3/users.rs | 34 +++++++++------------- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/apps/labrinth/src/auth/checks.rs b/apps/labrinth/src/auth/checks.rs index 7cb644384..662a8b093 100644 --- a/apps/labrinth/src/auth/checks.rs +++ b/apps/labrinth/src/auth/checks.rs @@ -315,9 +315,13 @@ pub async fn filter_enlisted_version_ids( pub async fn is_visible_collection( collection_data: &DBCollection, user_option: &Option, + hide_unlisted: bool, ) -> Result { - let mut authorized = !collection_data.status.is_hidden() - && !collection_data.projects.is_empty(); + let mut authorized = (if hide_unlisted { + collection_data.status.is_searchable() + } else { + !collection_data.status.is_hidden() + }) && !collection_data.projects.is_empty(); if let Some(user) = &user_option { if !authorized && (user.role.is_mod() || user.id == collection_data.user_id.into()) @@ -331,12 +335,17 @@ pub async fn is_visible_collection( pub async fn filter_visible_collections( collections: Vec, user_option: &Option, + hide_unlisted: bool, ) -> Result, ApiError> { let mut return_collections = Vec::new(); let mut check_collections = Vec::new(); for collection in collections { - if (!collection.status.is_hidden() && !collection.projects.is_empty()) + if ((if hide_unlisted { + collection.status.is_searchable() + } else { + !collection.status.is_hidden() + }) && !collection.projects.is_empty()) || user_option.as_ref().is_some_and(|x| x.role.is_mod()) { return_collections.push(collection.into()); diff --git a/apps/labrinth/src/models/v3/collections.rs b/apps/labrinth/src/models/v3/collections.rs index 652a44d2e..6bcdd538f 100644 --- a/apps/labrinth/src/models/v3/collections.rs +++ b/apps/labrinth/src/models/v3/collections.rs @@ -92,7 +92,7 @@ impl CollectionStatus { } } - // Project pages + info cannot be viewed + // Collection pages + info cannot be viewed pub fn is_hidden(&self) -> bool { match self { CollectionStatus::Rejected => true, @@ -103,6 +103,11 @@ impl CollectionStatus { } } + // Collection can be displayed in on user page + pub fn is_searchable(&self) -> bool { + matches!(self, CollectionStatus::Listed) + } + pub fn is_approved(&self) -> bool { match self { CollectionStatus::Listed => true, diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index 6f795de95..ab8058e0f 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -163,7 +163,8 @@ pub async fn collections_get( .ok(); let collections = - filter_visible_collections(collections_data, &user_option).await?; + filter_visible_collections(collections_data, &user_option, false) + .await?; Ok(HttpResponse::Ok().json(collections)) } @@ -192,7 +193,7 @@ pub async fn collection_get( .ok(); if let Some(data) = collection_data { - if is_visible_collection(&data, &user_option).await? { + if is_visible_collection(&data, &user_option, false).await? { return Ok(HttpResponse::Ok().json(Collection::from(data))); } } diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index d11c6b2d3..9f48fc6b8 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -1,14 +1,14 @@ use std::{collections::HashMap, sync::Arc}; use super::{ApiError, oauth_clients::get_user_clients}; -use crate::file_hosting::FileHostPublicity; -use crate::util::img::delete_old_images; use crate::{ - auth::{filter_visible_projects, get_user_from_headers}, + auth::{ + filter_visible_collections, filter_visible_projects, + get_user_from_headers, + }, database::{models::DBUser, redis::RedisPool}, - file_hosting::FileHost, + file_hosting::{FileHost, FileHostPublicity}, models::{ - collections::{Collection, CollectionStatus}, notifications::Notification, pats::Scopes, projects::Project, @@ -16,7 +16,7 @@ use crate::{ }, queue::session::AuthQueue, util::{ - routes::read_limited_from_payload, + img::delete_old_images, routes::read_limited_from_payload, validate::validation_errors_to_string, }, }; @@ -244,27 +244,19 @@ pub async fn collections_list( let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?; if let Some(id) = id_option.map(|x| x.id) { - let user_id: UserId = id.into(); - - let can_view_private = - user.is_some_and(|y| y.role.is_mod() || y.id == user_id); - - let project_data = DBUser::get_collections(id, &**pool).await?; + let collection_data = DBUser::get_collections(id, &**pool).await?; let response: Vec<_> = crate::database::models::DBCollection::get_many( - &project_data, + &collection_data, &**pool, &redis, ) - .await? - .into_iter() - .filter(|x| { - can_view_private || matches!(x.status, CollectionStatus::Listed) - }) - .map(Collection::from) - .collect(); + .await?; - Ok(HttpResponse::Ok().json(response)) + let collections = + filter_visible_collections(response, &user, true).await?; + + Ok(HttpResponse::Ok().json(collections)) } else { Err(ApiError::NotFound) }