From 8c779b2f2ef232a54a09d1bc459e7df837346de9 Mon Sep 17 00:00:00 2001 From: trisua Date: Mon, 4 Aug 2025 13:23:27 -0400 Subject: [PATCH] add: individual topic write permissions --- .../src/public/html/communities/settings.lisp | 33 +++++++++++++++---- .../src/public/html/communities/topic.lisp | 2 ++ .../routes/api/v1/communities/communities.rs | 9 ++++- crates/app/src/routes/api/v1/mod.rs | 2 ++ crates/core/src/database/posts.rs | 32 +++++++++++++++++- crates/core/src/model/communities.rs | 11 ++++++- 6 files changed, 80 insertions(+), 9 deletions(-) diff --git a/crates/app/src/public/html/communities/settings.lisp b/crates/app/src/public/html/communities/settings.lisp index 239d3cc..29b171e 100644 --- a/crates/app/src/public/html/communities/settings.lisp +++ b/crates/app/src/public/html/communities/settings.lisp @@ -686,12 +686,12 @@ (div ("class" "flex flex_col gap_1") (label - ("for" "description") + ("for" "{{ id }}-description") (str (text "communities:label.description"))) (input ("type" "text") ("name" "description") - ("id" "description") + ("id" "{{ id }}-description") ("placeholder" "description") ("value" "{{ topic.description }}") ("required" "") @@ -700,12 +700,12 @@ (div ("class" "flex flex_col gap_1") (label - ("for" "color") + ("for" "{{ id }}-color") (str (text "communities:label.color"))) (input ("type" "color") ("name" "color") - ("id" "color") + ("id" "{{ id }}-color") ("placeholder" "color") ("required" "") ("value" "{{ topic.color }}") @@ -713,17 +713,37 @@ (div ("class" "flex flex_col gap_1") (label - ("for" "position") + ("for" "{{ id }}-position") (str (text "communities:label.position"))) (input ("type" "number") ("name" "position") - ("id" "position") + ("id" "{{ id }}-position") ("placeholder" "position") ("required" "") ("value" "{{ topic.position }}") ("min" "0") ("max" "256"))) + (div + ("class" "flex flex_col gap_1") + (label + ("for" "{{ id }}-write_access") + (text "Post permission")) + (select + ("name" "write_access") + ("id" "{{ id }}-write_access") + (option + ("value" "Everybody") + ("selected" "{% if topic.write_access == 'Everybody' -%}true{% else %}false{%- endif %}") + (text "Everybody")) + (option + ("value" "Joined") + ("selected" "{% if topic.write_access == 'Joined' -%}true{% else %}false{%- endif %}") + (text "Joined")) + (option + ("value" "Owner") + ("selected" "{% if topic.write_access == 'Owner' -%}true{% else %}false{%- endif %}") + (text "Owner only")))) (button (icon (text "check")) (str (text "general:action.save"))))))) @@ -794,6 +814,7 @@ description: e.target.description.value, color: e.target.color.value, position: Number.parseInt(e.target.position.value), + write_access: e.target.write_access.selectedOptions[0].value, }), }) .then((res) => res.json()) diff --git a/crates/app/src/public/html/communities/topic.lisp b/crates/app/src/public/html/communities/topic.lisp index ce14857..08ed014 100644 --- a/crates/app/src/public/html/communities/topic.lisp +++ b/crates/app/src/public/html/communities/topic.lisp @@ -9,6 +9,7 @@ (text "{{ components::topic_display(id=topic_id, topic=topic, community=community, show_description=false) }}") (div ("class" "flex gap_2") + (text "{% if can_post -%}") (a ("href" "/communities/intents/post?community={{ community.id }}&topic={{ topic_id }}&sig=true") ("class" "button small lowered") @@ -16,6 +17,7 @@ (icon (text "plus")) (span (str (text "general:action.post")))) + (text "{%- endif %}") (a ("href" "/community/{{ community.title }}") ("class" "button lowered small") diff --git a/crates/app/src/routes/api/v1/communities/communities.rs b/crates/app/src/routes/api/v1/communities/communities.rs index 39bfea5..066c8a5 100644 --- a/crates/app/src/routes/api/v1/communities/communities.rs +++ b/crates/app/src/routes/api/v1/communities/communities.rs @@ -560,7 +560,13 @@ pub async fn add_topic_request( } // ... - let (topic_id, topic) = ForumTopic::new(req.title, req.description, req.color, req.position); + let (topic_id, topic) = ForumTopic::new( + req.title, + req.description, + req.color, + req.position, + community.write_access, + ); community.topics.insert(topic_id, topic); match data @@ -616,6 +622,7 @@ pub async fn update_topic_request( description: req.description, color: req.color, position: req.position, + write_access: req.write_access, }; community.topics.insert(topic_id, topic); diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index 5f553a6..2b148df 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -797,6 +797,8 @@ pub struct AddTopic { pub color: String, #[serde(default)] pub position: i32, + #[serde(default)] + pub write_access: CommunityWriteAccess, } #[derive(Deserialize)] diff --git a/crates/core/src/database/posts.rs b/crates/core/src/database/posts.rs index a3e7336..bd326f9 100644 --- a/crates/core/src/database/posts.rs +++ b/crates/core/src/database/posts.rs @@ -1783,6 +1783,28 @@ impl DataManager { } } + /// Check if the given `uid` can post in the given `community` with the given `access`. + pub async fn check_can_post_with_access( + &self, + community: &Community, + access: &CommunityWriteAccess, + uid: usize, + ) -> bool { + match *access { + CommunityWriteAccess::Owner => uid == community.owner, + CommunityWriteAccess::Joined => { + match self + .get_membership_by_owner_community(uid, community.id) + .await + { + Ok(m) => m.role.check_member(), + Err(_) => false, + } + } + _ => true, + } + } + /// Create a new post in the database. /// /// # Arguments @@ -1840,7 +1862,15 @@ impl DataManager { )); } - if community.topics.get(&data.topic).is_none() { + if let Some(topic) = community.topics.get(&data.topic) { + // check permission + if !self + .check_can_post_with_access(&community, &topic.write_access, data.owner) + .await + { + return Err(Error::NotAllowed); + } + } else { return Err(Error::GeneralNotFound("topic".to_string())); } } diff --git a/crates/core/src/model/communities.rs b/crates/core/src/model/communities.rs index 2108847..5b5a869 100644 --- a/crates/core/src/model/communities.rs +++ b/crates/core/src/model/communities.rs @@ -541,6 +541,8 @@ pub struct ForumTopic { pub description: String, pub color: String, pub position: i32, + #[serde(default)] + pub write_access: CommunityWriteAccess, } impl ForumTopic { @@ -549,7 +551,13 @@ impl ForumTopic { /// # Returns /// * ID for [`Community`] hashmap /// * [`ForumTopic`] - pub fn new(title: String, description: String, color: String, position: i32) -> (usize, Self) { + pub fn new( + title: String, + description: String, + color: String, + position: i32, + write_access: CommunityWriteAccess, + ) -> (usize, Self) { ( Snowflake::new().to_string().parse::().unwrap(), Self { @@ -557,6 +565,7 @@ impl ForumTopic { description, color, position, + write_access, }, ) }