From 8c3024cb40c96c96ed1dfeb21592258df2279c05 Mon Sep 17 00:00:00 2001 From: trisua Date: Thu, 8 May 2025 20:08:43 -0400 Subject: [PATCH] add: post tags --- crates/app/src/langs/en-US.toml | 1 + .../public/html/communities/create_post.html | 17 ++++ crates/app/src/public/html/components.html | 10 +++ crates/app/src/public/html/post/post.html | 17 +++- crates/app/src/public/html/profile/posts.html | 7 +- crates/app/src/public/js/atto.js | 6 +- crates/app/src/routes/pages/mod.rs | 2 + crates/app/src/routes/pages/profile.rs | 81 ++++++++++++------- .../database/drivers/sql/create_requests.sql | 2 +- crates/core/src/database/posts.rs | 62 ++++++++++++++ crates/core/src/database/requests.rs | 4 +- crates/core/src/model/communities.rs | 3 + sql_changes/requests_pkey.sql | 2 + 13 files changed, 180 insertions(+), 34 deletions(-) create mode 100644 sql_changes/requests_pkey.sql diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index 3b45ceb..f6109d1 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -56,6 +56,7 @@ version = "1.0.0" "auth:label.relationship" = "Relationship" "auth:label.joined_communities" = "Joined communities" "auth:label.recent_posts" = "Recent posts" +"auth:label.recent_with_tag" = "Recent posts (with tag)" "auth:label.before_you_view" = "Before you view" "auth:label.private_profile" = "Private profile" "auth:label.private_profile_message" = "This profile is private, meaning you can only view it if they follow you." diff --git a/crates/app/src/public/html/communities/create_post.html b/crates/app/src/public/html/communities/create_post.html index 13bdac2..1d84d42 100644 --- a/crates/app/src/public/html/communities/create_post.html +++ b/crates/app/src/public/html/communities/create_post.html @@ -197,6 +197,7 @@ reactions_enabled: true, is_nsfw: false, content_warning: "", + tags: "", }; window.BLANK_INITIAL_SETTINGS = JSON.stringify( @@ -238,12 +239,28 @@ window.POST_INITIAL_SETTINGS.content_warning, "textarea", ], + [ + ["tags", "Tags"], + window.POST_INITIAL_SETTINGS.tags, + "input", + { + embed_html: + 'Tags should be separated by a comma.', + }, + ], ]; trigger("ui::generate_settings_ui", [ document.getElementById("post_options"), settings_fields, window.POST_INITIAL_SETTINGS, + { + tags: (new_tags) => { + window.POST_INITIAL_SETTINGS.tags = new_tags + .split(",") + .map((t) => t.trim()); + }, + }, ]); }, 250); diff --git a/crates/app/src/public/html/components.html b/crates/app/src/public/html/components.html index de42975..c077c5e 100644 --- a/crates/app/src/public/html/components.html +++ b/crates/app/src/public/html/components.html @@ -253,6 +253,16 @@ and show_community and community.id != config.town_square or question %} {% endif %} + +
+ {% for tag in post.context.tags %} + #{{ tag }} + {% endfor %} +
diff --git a/crates/app/src/public/html/post/post.html b/crates/app/src/public/html/post/post.html index b904bd8..dd336ad 100644 --- a/crates/app/src/public/html/post/post.html +++ b/crates/app/src/public/html/post/post.html @@ -168,6 +168,15 @@ settings.content_warning, "textarea", ], + [ + ["tags", "Tags"], + settings.tags.join(", "), + "input", + { + embed_html: + 'Tags should be separated by a comma.', + }, + ], ]; if (can_manage_pins) { @@ -186,7 +195,13 @@ ]); } - ui.generate_settings_ui(element, settings_fields, settings); + ui.generate_settings_ui(element, settings_fields, settings, { + tags: (new_tags) => { + settings.tags = new_tags + .split(",") + .map((t) => t.trim()); + }, + }); }, 250); diff --git a/crates/app/src/public/html/profile/posts.html b/crates/app/src/public/html/profile/posts.html index 6e599f5..9eb46bf 100644 --- a/crates/app/src/public/html/profile/posts.html +++ b/crates/app/src/public/html/profile/posts.html @@ -5,7 +5,7 @@ profile.settings.allow_anonymous_questions) %} {{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header) }} -{% endif %} {% if pinned|length != 0 %} +{% endif %} {% if not tag and pinned|length != 0 %}
{{ icon "pin" }} @@ -29,8 +29,11 @@ profile.settings.allow_anonymous_questions) %}
- {{ icon "clock" }} + {% if not tag %} {{ icon "clock" }} {{ text "auth:label.recent_posts" }} + {% else %} {{ icon "tag" }} + {{ text "auth:label.recent_with_tag" }}: {{ tag }} + {% endif %}
diff --git a/crates/app/src/public/js/atto.js b/crates/app/src/public/js/atto.js index 022abb2..05335bb 100644 --- a/crates/app/src/public/js/atto.js +++ b/crates/app/src/public/js/atto.js @@ -858,7 +858,7 @@ media_theme_pref();
-
+
<${option.input_element_type || "input"} type="text" onchange="window.set_setting_field('${option.key}', event.target.value)" @@ -867,6 +867,8 @@ media_theme_pref(); id="${option.key}" ${option.input_element_type === "input" ? `value="${option.value}"/>` : ">"} ${option.input_element_type === "textarea" ? `${option.value}` : ""} + + ${(option.attributes || { embed_html: "" }).embed_html}
`; }); @@ -885,7 +887,7 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} } window.set_setting_field = (key, value) => { - if (settings_ref) { + if (settings_ref && !key_map[key]) { settings_ref[key] = value; } else { key_map[key](value); diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index bfcfafa..d931867 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -140,6 +140,8 @@ pub struct ProfileQuery { pub page: usize, #[serde(default)] pub warning: bool, + #[serde(default)] + pub tag: String, } #[derive(Deserialize)] diff --git a/crates/app/src/routes/pages/profile.rs b/crates/app/src/routes/pages/profile.rs index 5f10b3e..b905953 100644 --- a/crates/app/src/routes/pages/profile.rs +++ b/crates/app/src/routes/pages/profile.rs @@ -198,40 +198,66 @@ pub async fn posts_request( Vec::new() }; - let posts = match data - .0 - .get_posts_by_user(other_user.id, 12, props.page, &user) - .await - { - Ok(p) => match data + let posts = if props.tag.is_empty() { + match data .0 - .fill_posts_with_community( - p, - if let Some(ref ua) = user { ua.id } else { 0 }, - &ignore_users, - ) + .get_posts_by_user(other_user.id, 12, props.page, &user) .await { - Ok(p) => p, + Ok(p) => match data + .0 + .fill_posts_with_community( + p, + if let Some(ref ua) = user { ua.id } else { 0 }, + &ignore_users, + ) + .await + { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + }, Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), - }, - Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + } + } else { + match data + .0 + .get_posts_by_user_tag(other_user.id, &props.tag, 12, props.page, &user) + .await + { + Ok(p) => match data + .0 + .fill_posts_with_community( + p, + if let Some(ref ua) = user { ua.id } else { 0 }, + &ignore_users, + ) + .await + { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + }, + Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + } }; - let pinned = match data.0.get_pinned_posts_by_user(other_user.id).await { - Ok(p) => match data - .0 - .fill_posts_with_community( - p, - if let Some(ref ua) = user { ua.id } else { 0 }, - &ignore_users, - ) - .await - { - Ok(p) => p, + let pinned = if props.tag.is_empty() { + match data.0.get_pinned_posts_by_user(other_user.id).await { + Ok(p) => match data + .0 + .fill_posts_with_community( + p, + if let Some(ref ua) = user { ua.id } else { 0 }, + &ignore_users, + ) + .await + { + Ok(p) => Some(p), + Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + }, Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), - }, - Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)), + } + } else { + None }; let communities = match data.0.get_memberships_by_owner(other_user.id).await { @@ -282,6 +308,7 @@ pub async fn posts_request( context.insert("posts", &posts); context.insert("pinned", &pinned); context.insert("page", &props.page); + context.insert("tag", &props.tag); profile_context( &mut context, &user, diff --git a/crates/core/src/database/drivers/sql/create_requests.sql b/crates/core/src/database/drivers/sql/create_requests.sql index fb64684..d134c0b 100644 --- a/crates/core/src/database/drivers/sql/create_requests.sql +++ b/crates/core/src/database/drivers/sql/create_requests.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS requests ( - id BIGINT NOT NULL PRIMARY KEY, + id BIGINT NOT NULL, created BIGINT NOT NULL, owner BIGINT NOT NULL, action_type TEXT NOT NULL, diff --git a/crates/core/src/database/posts.rs b/crates/core/src/database/posts.rs index 9c70817..1941f4e 100644 --- a/crates/core/src/database/posts.rs +++ b/crates/core/src/database/posts.rs @@ -298,6 +298,68 @@ impl DataManager { Ok(res.unwrap()) } + /// Get all posts from the given user with the given tag (from most recent). + /// + /// # Arguments + /// * `id` - the ID of the user the requested posts belong to + /// * `tag` - the tag to filter by + /// * `batch` - the limit of posts in each page + /// * `page` - the page number + pub async fn get_posts_by_user_tag( + &self, + id: usize, + tag: &str, + batch: usize, + page: usize, + user: &Option, + ) -> Result> { + let other_user = self.get_user_by_id(id).await?; + + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + // check if we should hide nsfw posts + let mut hide_nsfw: bool = true; + + if let Some(ua) = user { + if ua.id == other_user.id { + hide_nsfw = false + } + } + + if other_user.settings.private_profile { + hide_nsfw = false; + } + + // ... + let res = query_rows!( + &conn, + &format!( + "SELECT * FROM posts WHERE owner = $1 AND context::json->>'tags' LIKE $2 {} ORDER BY created DESC LIMIT $3 OFFSET $4", + if hide_nsfw { + "AND NOT context LIKE '%\"is_nsfw\":true%'" + } else { + "" + } + ), + params![ + &(id as i64), + &format!("%\"{tag}\"%"), + &(batch as i64), + &((page * batch) as i64) + ], + |x| { Self::get_post_from_row(x) } + ); + + if res.is_err() { + return Err(Error::GeneralNotFound("post".to_string())); + } + + Ok(res.unwrap()) + } + /// Get all posts from the given community (from most recent). /// /// # Arguments diff --git a/crates/core/src/database/requests.rs b/crates/core/src/database/requests.rs index 3786cdc..e554e7d 100644 --- a/crates/core/src/database/requests.rs +++ b/crates/core/src/database/requests.rs @@ -130,7 +130,9 @@ impl DataManager { .get_request_by_id_linked_asset(id, linked_asset) .await?; - if !force && user.id != y.owner && !user.permissions.check(FinePermission::MANAGE_REQUESTS) + if !force + && (user.id != y.owner && user.id != y.linked_asset) + && !user.permissions.check(FinePermission::MANAGE_REQUESTS) { return Err(Error::NotAllowed); } diff --git a/crates/core/src/model/communities.rs b/crates/core/src/model/communities.rs index 47e9825..dc014d3 100644 --- a/crates/core/src/model/communities.rs +++ b/crates/core/src/model/communities.rs @@ -174,6 +174,8 @@ pub struct PostContext { pub reactions_enabled: bool, #[serde(default)] pub content_warning: String, + #[serde(default)] + pub tags: Vec, } fn default_comments_enabled() -> bool { @@ -201,6 +203,7 @@ impl Default for PostContext { answering: 0, reactions_enabled: default_reactions_enabled(), content_warning: String::new(), + tags: Vec::new(), } } } diff --git a/sql_changes/requests_pkey.sql b/sql_changes/requests_pkey.sql new file mode 100644 index 0000000..e7870f1 --- /dev/null +++ b/sql_changes/requests_pkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE requests +DROP CONSTRAINT requests_pkey;