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
This commit is contained in:
Emma Alexia 2025-07-27 14:23:49 -04:00 committed by GitHub
parent ff88724d01
commit b8982a6d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 27 deletions

View File

@ -315,9 +315,13 @@ pub async fn filter_enlisted_version_ids(
pub async fn is_visible_collection( pub async fn is_visible_collection(
collection_data: &DBCollection, collection_data: &DBCollection,
user_option: &Option<User>, user_option: &Option<User>,
hide_unlisted: bool,
) -> Result<bool, ApiError> { ) -> Result<bool, ApiError> {
let mut authorized = !collection_data.status.is_hidden() let mut authorized = (if hide_unlisted {
&& !collection_data.projects.is_empty(); collection_data.status.is_searchable()
} else {
!collection_data.status.is_hidden()
}) && !collection_data.projects.is_empty();
if let Some(user) = &user_option { if let Some(user) = &user_option {
if !authorized if !authorized
&& (user.role.is_mod() || user.id == collection_data.user_id.into()) && (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( pub async fn filter_visible_collections(
collections: Vec<DBCollection>, collections: Vec<DBCollection>,
user_option: &Option<User>, user_option: &Option<User>,
hide_unlisted: bool,
) -> Result<Vec<crate::models::collections::Collection>, ApiError> { ) -> Result<Vec<crate::models::collections::Collection>, ApiError> {
let mut return_collections = Vec::new(); let mut return_collections = Vec::new();
let mut check_collections = Vec::new(); let mut check_collections = Vec::new();
for collection in collections { 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()) || user_option.as_ref().is_some_and(|x| x.role.is_mod())
{ {
return_collections.push(collection.into()); return_collections.push(collection.into());

View File

@ -92,7 +92,7 @@ impl CollectionStatus {
} }
} }
// Project pages + info cannot be viewed // Collection pages + info cannot be viewed
pub fn is_hidden(&self) -> bool { pub fn is_hidden(&self) -> bool {
match self { match self {
CollectionStatus::Rejected => true, 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 { pub fn is_approved(&self) -> bool {
match self { match self {
CollectionStatus::Listed => true, CollectionStatus::Listed => true,

View File

@ -163,7 +163,8 @@ pub async fn collections_get(
.ok(); .ok();
let collections = let collections =
filter_visible_collections(collections_data, &user_option).await?; filter_visible_collections(collections_data, &user_option, false)
.await?;
Ok(HttpResponse::Ok().json(collections)) Ok(HttpResponse::Ok().json(collections))
} }
@ -192,7 +193,7 @@ pub async fn collection_get(
.ok(); .ok();
if let Some(data) = collection_data { 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))); return Ok(HttpResponse::Ok().json(Collection::from(data)));
} }
} }

View File

@ -1,14 +1,14 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use super::{ApiError, oauth_clients::get_user_clients}; use super::{ApiError, oauth_clients::get_user_clients};
use crate::file_hosting::FileHostPublicity;
use crate::util::img::delete_old_images;
use crate::{ 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}, database::{models::DBUser, redis::RedisPool},
file_hosting::FileHost, file_hosting::{FileHost, FileHostPublicity},
models::{ models::{
collections::{Collection, CollectionStatus},
notifications::Notification, notifications::Notification,
pats::Scopes, pats::Scopes,
projects::Project, projects::Project,
@ -16,7 +16,7 @@ use crate::{
}, },
queue::session::AuthQueue, queue::session::AuthQueue,
util::{ util::{
routes::read_limited_from_payload, img::delete_old_images, routes::read_limited_from_payload,
validate::validation_errors_to_string, 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?; let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) { if let Some(id) = id_option.map(|x| x.id) {
let user_id: UserId = id.into(); let collection_data = DBUser::get_collections(id, &**pool).await?;
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 response: Vec<_> = crate::database::models::DBCollection::get_many( let response: Vec<_> = crate::database::models::DBCollection::get_many(
&project_data, &collection_data,
&**pool, &**pool,
&redis, &redis,
) )
.await? .await?;
.into_iter()
.filter(|x| {
can_view_private || matches!(x.status, CollectionStatus::Listed)
})
.map(Collection::from)
.collect();
Ok(HttpResponse::Ok().json(response)) let collections =
filter_visible_collections(response, &user, true).await?;
Ok(HttpResponse::Ok().json(collections))
} else { } else {
Err(ApiError::NotFound) Err(ApiError::NotFound)
} }