From d42375441f3a0031704cec60e2adf072d87d040d Mon Sep 17 00:00:00 2001 From: trisua Date: Thu, 24 Apr 2025 16:40:03 -0400 Subject: [PATCH] add: allow users to block users who have blocked them/private users fix: anonymous post page panic add: allow users to select home timeline --- Cargo.lock | 8 +- crates/app/Cargo.toml | 2 +- crates/app/src/assets.rs | 9 +- crates/app/src/langs/en-US.toml | 2 + crates/app/src/macros.rs | 82 ++++++++++++ crates/app/src/public/html/macros.html | 2 +- crates/app/src/public/html/post/post.html | 3 +- .../app/src/public/html/profile/blocked.html | 65 ++++++++++ .../app/src/public/html/profile/private.html | 31 +++++ .../app/src/public/html/profile/settings.html | 70 +++++++++++ crates/app/src/routes/pages/profile.rs | 118 ++---------------- crates/core/Cargo.toml | 2 +- crates/core/src/model/auth.rs | 37 ++++++ crates/l10n/Cargo.toml | 2 +- crates/shared/Cargo.toml | 2 +- 15 files changed, 313 insertions(+), 122 deletions(-) create mode 100644 crates/app/src/public/html/profile/blocked.html diff --git a/Cargo.lock b/Cargo.lock index 387fb13..5ad7dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "tetratto" -version = "1.0.6" +version = "1.0.7" dependencies = [ "ammonia", "axum", @@ -3244,7 +3244,7 @@ dependencies = [ [[package]] name = "tetratto-core" -version = "1.0.6" +version = "1.0.7" dependencies = [ "async-recursion", "bb8-postgres", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "tetratto-l10n" -version = "1.0.6" +version = "1.0.7" dependencies = [ "pathbufd", "serde", @@ -3272,7 +3272,7 @@ dependencies = [ [[package]] name = "tetratto-shared" -version = "1.0.6" +version = "1.0.7" dependencies = [ "ammonia", "chrono", diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index b33a3e8..73e5a1c 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tetratto" -version = "1.0.6" +version = "1.0.7" edition = "2024" [features] diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 977aa10..def0aba 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -6,7 +6,10 @@ use std::{ sync::LazyLock, }; use tera::Context; -use tetratto_core::{config::Config, model::auth::User}; +use tetratto_core::{ + config::Config, + model::auth::{DefaultTimelineChoice, User}, +}; use tetratto_l10n::LangFile; use tetratto_shared::hash::salt; use tokio::sync::RwLock; @@ -48,6 +51,7 @@ pub const PROFILE_FOLLOWING: &str = include_str!("./public/html/profile/followin pub const PROFILE_FOLLOWERS: &str = include_str!("./public/html/profile/followers.html"); pub const PROFILE_WARNING: &str = include_str!("./public/html/profile/warning.html"); pub const PROFILE_PRIVATE: &str = include_str!("./public/html/profile/private.html"); +pub const PROFILE_BLOCKED: &str = include_str!("./public/html/profile/blocked.html"); pub const COMMUNITIES_LIST: &str = include_str!("./public/html/communities/list.html"); pub const COMMUNITIES_BASE: &str = include_str!("./public/html/communities/base.html"); @@ -197,6 +201,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"profile/followers.html"(crate::assets::PROFILE_FOLLOWERS) --config=config); write_template!(html_path->"profile/warning.html"(crate::assets::PROFILE_WARNING) --config=config); write_template!(html_path->"profile/private.html"(crate::assets::PROFILE_PRIVATE) --config=config); + write_template!(html_path->"profile/blocked.html"(crate::assets::PROFILE_BLOCKED) --config=config); write_template!(html_path->"communities/list.html"(crate::assets::COMMUNITIES_LIST) -d "communities" --config=config); write_template!(html_path->"communities/base.html"(crate::assets::COMMUNITIES_BASE) --config=config); @@ -284,9 +289,11 @@ pub(crate) async fn initial_context( if let Some(ua) = user { ctx.insert("is_helper", &ua.permissions.check_helper()); ctx.insert("is_manager", &ua.permissions.check_manager()); + ctx.insert("home", ua.settings.default_timeline.relative_url()); } else { ctx.insert("is_helper", &false); ctx.insert("is_manager", &false); + ctx.insert("home", DefaultTimelineChoice::default().relative_url()); } ctx.insert("lang", &lang.data); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index a510c92..5066906 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -58,6 +58,8 @@ version = "1.0.0" "auth:label.private_profile_message" = "This profile is private, meaning you can only view it if they follow you." "auth:action.request_to_follow" = "Request to follow" "auth:action.cancel_follow_request" = "Cancel follow request" +"auth:label.blocked_profile" = "You're blocked" +"auth:label.blocked_profile_message" = "This user has blocked you." "communities:action.create" = "Create" "communities:action.select" = "Select" diff --git a/crates/app/src/macros.rs b/crates/app/src/macros.rs index 5966377..e16c061 100644 --- a/crates/app/src/macros.rs +++ b/crates/app/src/macros.rs @@ -94,3 +94,85 @@ macro_rules! get_lang { } }}; } + +#[macro_export] +macro_rules! check_user_blocked_or_private { + ($user:ident, $other_user:ident, $data:ident, $jar:ident) => { + // check if we're blocked + if let Some(ref ua) = $user { + if $data + .0 + .get_userblock_by_initiator_receiver($other_user.id, ua.id) + .await + .is_ok() + { + let lang = get_lang!($jar, $data.0); + let mut context = initial_context(&$data.0.0, lang, &$user).await; + + context.insert("profile", &$other_user); + context.insert( + "is_blocking", + &$data + .0 + .get_userblock_by_initiator_receiver(ua.id, $other_user.id) + .await + .is_ok(), + ); + + return Ok(Html( + $data.1.render("profile/blocked.html", &context).unwrap(), + )); + } + } + + // check for private profile + if $other_user.settings.private_profile { + if let Some(ref ua) = $user { + if (ua.id != $other_user.id) + && !ua.permissions.check(FinePermission::MANAGE_USERS) + && $data + .0 + .get_userfollow_by_initiator_receiver($other_user.id, ua.id) + .await + .is_err() + { + let lang = get_lang!($jar, $data.0); + let mut context = initial_context(&$data.0.0, lang, &$user).await; + + context.insert("profile", &$other_user); + context.insert( + "follow_requested", + &$data + .0 + .get_request_by_id_linked_asset(ua.id, $other_user.id) + .await + .is_ok(), + ); + context.insert( + "is_blocking", + &$data + .0 + .get_userblock_by_initiator_receiver(ua.id, $other_user.id) + .await + .is_ok(), + ); + + return Ok(Html( + $data.1.render("profile/private.html", &context).unwrap(), + )); + } + } else { + let lang = get_lang!($jar, $data.0); + let mut context = initial_context(&$data.0.0, lang, &$user).await; + + context.insert("profile", &$other_user); + context.insert("follow_requested", &false); + context.insert("is_following", &false); + + return Ok(Html( + $data.1.render("profile/private.html", &context).unwrap(), + )); + } + } + }; +} diff --git a/crates/app/src/public/html/macros.html b/crates/app/src/public/html/macros.html index 4b57266..381e645 100644 --- a/crates/app/src/public/html/macros.html +++ b/crates/app/src/public/html/macros.html @@ -8,7 +8,7 @@ {% if show_lhs %} {{ icon "house" }} diff --git a/crates/app/src/public/html/post/post.html b/crates/app/src/public/html/post/post.html index d6fbd86..d17182c 100644 --- a/crates/app/src/public/html/post/post.html +++ b/crates/app/src/public/html/post/post.html @@ -125,7 +125,8 @@ ui.refresh_container(element, []); const can_manage_pins = "{{ can_manage_pins }}" === "true"; - const is_owner = "{{ user.id == post.owner }}" === "true"; + const is_owner = + "{{ user and user.id == post.owner }}" === "true"; const settings_fields = [ [ diff --git a/crates/app/src/public/html/profile/blocked.html b/crates/app/src/public/html/profile/blocked.html new file mode 100644 index 0000000..ebae7c7 --- /dev/null +++ b/crates/app/src/public/html/profile/blocked.html @@ -0,0 +1,65 @@ +{% extends "root.html" %} {% block head %} +{{ profile.username }} (blocked) - {{ config.name }} +{% endblock %} {% block body %} {{ macros::nav() }} +
+ +
+{% endblock %} diff --git a/crates/app/src/public/html/profile/private.html b/crates/app/src/public/html/profile/private.html index d31e924..65e2850 100644 --- a/crates/app/src/public/html/profile/private.html +++ b/crates/app/src/public/html/profile/private.html @@ -45,6 +45,16 @@ {{ icon "user-minus" }} {{ text "auth:action.unfollow" }} + {% endif %} {% if not is_blocking %} + + {% else %} + {% endif %} {% endif %} diff --git a/crates/app/src/public/html/profile/settings.html b/crates/app/src/public/html/profile/settings.html index eb57ff3..089a76e 100644 --- a/crates/app/src/public/html/profile/settings.html +++ b/crates/app/src/public/html/profile/settings.html @@ -33,6 +33,75 @@
+
+
+ Home timeline +
+ +
+ + + This represents the timeline the home button takes you + to. +
+
+
{{ text "settings:label.change_password" }} @@ -737,6 +806,7 @@ const theme_settings = document.getElementById("theme_settings"); ui.refresh_container(account_settings, [ + "home_timeline", "change_password", "change_username", "two_factor_authentication", diff --git a/crates/app/src/routes/pages/profile.rs b/crates/app/src/routes/pages/profile.rs index 65eee16..3c30867 100644 --- a/crates/app/src/routes/pages/profile.rs +++ b/crates/app/src/routes/pages/profile.rs @@ -1,5 +1,8 @@ use super::{render_error, PaginatedQuery, ProfileQuery}; -use crate::{assets::initial_context, get_lang, get_user_from_token, sanitize::clean_settings, State}; +use crate::{ + assets::initial_context, check_user_blocked_or_private, get_lang, get_user_from_token, + sanitize::clean_settings, State, +}; use axum::{ Extension, extract::{Path, Query}, @@ -173,69 +176,7 @@ pub async fn posts_request( Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), }; - // check if we're blocked - if let Some(ref ua) = user { - if data - .0 - .get_userblock_by_initiator_receiver(other_user.id, ua.id) - .await - .is_ok() - { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &user).await, - )); - } - } - - // check for private profile - if other_user.settings.private_profile { - if let Some(ref ua) = user { - if (ua.id != other_user.id) - && !ua.permissions.check(FinePermission::MANAGE_USERS) - && data - .0 - .get_userfollow_by_initiator_receiver(other_user.id, ua.id) - .await - .is_err() - { - let lang = get_lang!(jar, data.0); - let mut context = initial_context(&data.0.0, lang, &user).await; - - context.insert("profile", &other_user); - context.insert( - "follow_requested", - &data - .0 - .get_request_by_id_linked_asset(ua.id, other_user.id) - .await - .is_ok(), - ); - context.insert( - "is_following", - &data - .0 - .get_userfollow_by_initiator_receiver(ua.id, other_user.id) - .await - .is_ok(), - ); - - return Ok(Html( - data.1.render("profile/private.html", &context).unwrap(), - )); - } - } else { - let lang = get_lang!(jar, data.0); - let mut context = initial_context(&data.0.0, lang, &user).await; - - context.insert("profile", &other_user); - context.insert("follow_requested", &false); - context.insert("is_following", &false); - - return Ok(Html( - data.1.render("profile/private.html", &context).unwrap(), - )); - } - } + check_user_blocked_or_private!(user, other_user, data, jar); // check for warning if props.warning { @@ -371,19 +312,7 @@ pub async fn following_request( Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), }; - // check if we're blocked - if let Some(ref ua) = user { - if data - .0 - .get_userblock_by_initiator_receiver(other_user.id, ua.id) - .await - .is_ok() - { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &user).await, - )); - } - } + check_user_blocked_or_private!(user, other_user, data, jar); // check for private profile if other_user.settings.private_profile { @@ -498,40 +427,7 @@ pub async fn followers_request( Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), }; - // check if we're blocked - if let Some(ref ua) = user { - if data - .0 - .get_userblock_by_initiator_receiver(other_user.id, ua.id) - .await - .is_ok() - { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &user).await, - )); - } - } - - // check for private profile - if other_user.settings.private_profile { - if let Some(ref ua) = user { - if ua.id != other_user.id - && data - .0 - .get_userfollow_by_initiator_receiver(other_user.id, ua.id) - .await - .is_err() - { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &user).await, - )); - } - } else { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &user).await, - )); - } - } + check_user_blocked_or_private!(user, other_user, data, jar); // fetch data let list = match data diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index ce16761..5031fda 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tetratto-core" -version = "1.0.6" +version = "1.0.7" edition = "2024" [features] diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index 7b7b52a..6c194fe 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -50,6 +50,40 @@ impl Default for ThemePreference { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DefaultTimelineChoice { + MyCommunities, + MyCommunitiesQuestions, + PopularPosts, + PopularQuestions, + FollowingPosts, + FollowingQuestions, + AllPosts, + AllQuestions, +} + +impl Default for DefaultTimelineChoice { + fn default() -> Self { + Self::MyCommunities + } +} + +impl DefaultTimelineChoice { + /// Get the relative URL that the timeline should bring you to. + pub fn relative_url(&self) -> &str { + match &self { + Self::MyCommunities => "/", + Self::MyCommunitiesQuestions => "/questions", + Self::PopularPosts => "/popular", + Self::PopularQuestions => "/popular/questions", + Self::FollowingPosts => "/following", + Self::FollowingQuestions => "/following/questions", + Self::AllPosts => "/all", + Self::AllQuestions => "/all/questions", + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct UserSettings { #[serde(default)] @@ -148,6 +182,9 @@ pub struct UserSettings { /// If dislikes are hidden for the user. #[serde(default)] pub hide_dislikes: bool, + /// The timeline that the "Home" button takes you to + #[serde(default)] + pub default_timeline: DefaultTimelineChoice, } impl Default for User { diff --git a/crates/l10n/Cargo.toml b/crates/l10n/Cargo.toml index dc4cad4..c3bf434 100644 --- a/crates/l10n/Cargo.toml +++ b/crates/l10n/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tetratto-l10n" -version = "1.0.6" +version = "1.0.7" edition = "2024" authors.workspace = true repository.workspace = true diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index c1fe509..85c3fda 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tetratto-shared" -version = "1.0.6" +version = "1.0.7" edition = "2024" authors.workspace = true repository.workspace = true