add: hide_from_social_lists user setting

This commit is contained in:
trisua 2025-07-10 13:32:43 -04:00
parent 4e152b07be
commit bdd8f9a869
5 changed files with 64 additions and 21 deletions

View file

@ -143,8 +143,7 @@
(text "{% endfor %}")) (text "{% endfor %}"))
(span (span
("class" "fade") ("class" "fade")
(text "This represents the timeline the home button takes you (text "This represents the timeline the home button takes you to."))))
to."))))
(div (div
("class" "card-nest desktop") ("class" "card-nest desktop")
("ui_ident" "notifications") ("ui_ident" "notifications")
@ -1540,6 +1539,14 @@
\"{{ profile.settings.hide_associated_blocked_users }}\", \"{{ profile.settings.hide_associated_blocked_users }}\",
\"checkbox\", \"checkbox\",
], ],
[
[
\"hide_from_social_lists\",
\"Hide my profile from social lists (followers/following)\",
],
\"{{ profile.settings.hide_from_social_lists }}\",
\"checkbox\",
],
[[], \"Questions\", \"title\"], [[], \"Questions\", \"title\"],
[ [
[ [

View file

@ -278,7 +278,10 @@ pub async fn followers_request(
Ok(f) => Json(ApiReturn { Ok(f) => Json(ApiReturn {
ok: true, ok: true,
message: "Success".to_string(), message: "Success".to_string(),
payload: match data.fill_userfollows_with_initiator(f).await { payload: match data
.fill_userfollows_with_initiator(f, &Some(user.clone()), id == user.id)
.await
{
Ok(f) => Some(data.userfollows_user_filter(&f)), Ok(f) => Some(data.userfollows_user_filter(&f)),
Err(e) => return Json(e.into()), Err(e) => return Json(e.into()),
}, },
@ -310,7 +313,10 @@ pub async fn following_request(
Ok(f) => Json(ApiReturn { Ok(f) => Json(ApiReturn {
ok: true, ok: true,
message: "Success".to_string(), message: "Success".to_string(),
payload: match data.fill_userfollows_with_receiver(f).await { payload: match data
.fill_userfollows_with_receiver(f, &Some(user.clone()), id == user.id)
.await
{
Ok(f) => Some(data.userfollows_user_filter(&f)), Ok(f) => Some(data.userfollows_user_filter(&f)),
Err(e) => return Json(e.into()), Err(e) => return Json(e.into()),
}, },

View file

@ -70,6 +70,8 @@ pub async fn settings_request(
.get_userfollows_by_initiator_all(profile.id) .get_userfollows_by_initiator_all(profile.id)
.await .await
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
&None,
false,
) )
.await .await
{ {
@ -718,7 +720,7 @@ pub async fn following_request(
.get_userfollows_by_initiator(other_user.id, 12, props.page) .get_userfollows_by_initiator(other_user.id, 12, props.page)
.await .await
{ {
Ok(l) => match data.0.fill_userfollows_with_receiver(l).await { Ok(l) => match data.0.fill_userfollows_with_receiver(l, &user, true).await {
Ok(l) => l, Ok(l) => l,
Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
}, },
@ -813,7 +815,7 @@ pub async fn followers_request(
.get_userfollows_by_receiver(other_user.id, 12, props.page) .get_userfollows_by_receiver(other_user.id, 12, props.page)
.await .await
{ {
Ok(l) => match data.0.fill_userfollows_with_initiator(l).await { Ok(l) => match data.0.fill_userfollows_with_initiator(l, &user, true).await {
Ok(l) => l, Ok(l) => l,
Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
}, },

View file

@ -195,18 +195,29 @@ impl DataManager {
pub async fn fill_userfollows_with_receiver( pub async fn fill_userfollows_with_receiver(
&self, &self,
userfollows: Vec<UserFollow>, userfollows: Vec<UserFollow>,
as_user: &Option<User>,
do_check: bool,
) -> Result<Vec<(UserFollow, User)>> { ) -> Result<Vec<(UserFollow, User)>> {
let mut out: Vec<(UserFollow, User)> = Vec::new(); let mut out: Vec<(UserFollow, User)> = Vec::new();
for userfollow in userfollows { for userfollow in userfollows {
let receiver = userfollow.receiver; let receiver = userfollow.receiver;
out.push(( let user = match self.get_user_by_id(receiver).await {
userfollow,
match self.get_user_by_id(receiver).await {
Ok(u) => u, Ok(u) => u,
Err(_) => continue, Err(_) => continue,
}, };
));
if user.settings.hide_from_social_lists && do_check {
if let Some(ua) = as_user {
if !ua.permissions.check(FinePermission::MANAGE_USERS) {
continue;
}
} else {
continue;
}
}
out.push((userfollow, user));
} }
Ok(out) Ok(out)
@ -216,18 +227,29 @@ impl DataManager {
pub async fn fill_userfollows_with_initiator( pub async fn fill_userfollows_with_initiator(
&self, &self,
userfollows: Vec<UserFollow>, userfollows: Vec<UserFollow>,
as_user: &Option<User>,
do_check: bool,
) -> Result<Vec<(UserFollow, User)>> { ) -> Result<Vec<(UserFollow, User)>> {
let mut out: Vec<(UserFollow, User)> = Vec::new(); let mut out: Vec<(UserFollow, User)> = Vec::new();
for userfollow in userfollows { for userfollow in userfollows {
let initiator = userfollow.initiator; let initiator = userfollow.initiator;
out.push(( let user = match self.get_user_by_id(initiator).await {
userfollow,
match self.get_user_by_id(initiator).await {
Ok(u) => u, Ok(u) => u,
Err(_) => continue, Err(_) => continue,
}, };
));
if user.settings.hide_from_social_lists && do_check {
if let Some(ua) = as_user {
if !ua.permissions.check(FinePermission::MANAGE_USERS) {
continue;
}
} else {
continue;
}
}
out.push((userfollow, user));
} }
Ok(out) Ok(out)

View file

@ -302,6 +302,12 @@ pub struct UserSettings {
/// Which tab is shown by default on the user's profile. /// Which tab is shown by default on the user's profile.
#[serde(default)] #[serde(default)]
pub default_profile_tab: DefaultProfileTabChoice, pub default_profile_tab: DefaultProfileTabChoice,
/// If the user is hidden from followers/following tabs.
///
/// The user will still impact the followers/following numbers, but will not
/// be shown in the UI (or API).
#[serde(default)]
pub hide_from_social_lists: bool,
} }
fn mime_avif() -> String { fn mime_avif() -> String {
@ -521,7 +527,7 @@ pub struct ExternalConnectionData {
} }
/// The total number of achievements needed to 100% Tetratto! /// The total number of achievements needed to 100% Tetratto!
pub const ACHIEVEMENTS: usize = 34; pub const ACHIEVEMENTS: usize = 36;
/// "self-serve" achievements can be granted by the user through the API. /// "self-serve" achievements can be granted by the user through the API.
pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] = &[ pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] = &[
AchievementName::OpenReference, AchievementName::OpenReference,