diff --git a/api_tests/package.json b/api_tests/package.json index 82d2f0eb7..20d919b28 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -20,7 +20,7 @@ "eslint": "^8.25.0", "eslint-plugin-prettier": "^4.0.0", "jest": "^27.0.6", - "lemmy-js-client": "0.17.0-rc.48", + "lemmy-js-client": "0.17.0-rc.56", "node-fetch": "^2.6.1", "prettier": "^2.7.1", "reflect-metadata": "^0.1.13", diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index d0e42c726..264b1cd7d 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -11,7 +11,7 @@ import { setupLogins, createPost, editPost, - stickyPost, + featurePost, lockPost, resolvePost, likePost, @@ -157,8 +157,8 @@ test("Sticky a post", async () => { let betaPost1 = ( await resolvePost(beta, postRes.post_view.post) ).post.unwrap(); - let stickiedPostRes = await stickyPost(beta, true, betaPost1.post); - expect(stickiedPostRes.post_view.post.stickied).toBe(true); + let stickiedPostRes = await featurePost(beta, true, betaPost1.post); + expect(stickiedPostRes.post_view.post.featured_community).toBe(true); // Make sure that post is stickied on beta let betaPost = ( @@ -166,11 +166,11 @@ test("Sticky a post", async () => { ).post.unwrap(); expect(betaPost.community.local).toBe(true); expect(betaPost.creator.local).toBe(false); - expect(betaPost.post.stickied).toBe(true); + expect(betaPost.post.featured_community).toBe(true); // Unsticky a post - let unstickiedPost = await stickyPost(beta, false, betaPost1.post); - expect(unstickiedPost.post_view.post.stickied).toBe(false); + let unstickiedPost = await featurePost(beta, false, betaPost1.post); + expect(unstickiedPost.post_view.post.featured_community).toBe(false); // Make sure that post is unstickied on beta let betaPost2 = ( @@ -178,18 +178,18 @@ test("Sticky a post", async () => { ).post.unwrap(); expect(betaPost2.community.local).toBe(true); expect(betaPost2.creator.local).toBe(false); - expect(betaPost2.post.stickied).toBe(false); + expect(betaPost2.post.featured_community).toBe(false); // Make sure that gamma cannot sticky the post on beta let gammaPost = ( await resolvePost(gamma, postRes.post_view.post) ).post.unwrap(); - let gammaTrySticky = await stickyPost(gamma, true, gammaPost.post); + let gammaTrySticky = await featurePost(gamma, true, gammaPost.post); let betaPost3 = ( await resolvePost(beta, postRes.post_view.post) ).post.unwrap(); - expect(gammaTrySticky.post_view.post.stickied).toBe(true); - expect(betaPost3.post.stickied).toBe(false); + expect(gammaTrySticky.post_view.post.featured_community).toBe(true); + expect(betaPost3.post.featured_community).toBe(false); }); test("Lock a post", async () => { diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 9bf232065..efce35f55 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -7,7 +7,6 @@ import { CreateComment, DeletePost, RemovePost, - StickyPost, LockPost, PostResponse, SearchResponse, @@ -64,6 +63,8 @@ import { CommentSortType, GetComments, GetCommentsResponse, + FeaturePost, + PostFeatureType, } from "lemmy-js-client"; export interface API { @@ -180,14 +181,13 @@ export async function setupLogins() { rate_limit_search: Some(999), rate_limit_search_per_second: None, federation_enabled: None, - federation_strict_allowlist: None, - federation_http_fetch_retry_limit: None, federation_worker_count: None, captcha_enabled: None, captcha_difficulty: None, allowed_instances: None, blocked_instances: None, auth: "", + taglines: None, }); // Set the blocks and auths for each @@ -293,17 +293,18 @@ export async function removePost( return api.client.removePost(form); } -export async function stickyPost( +export async function featurePost( api: API, - stickied: boolean, + featured: boolean, post: Post ): Promise { - let form = new StickyPost({ + let form = new FeaturePost({ post_id: post.id, - stickied, + featured, + feature_type: PostFeatureType.Community, auth: api.auth.unwrap(), }); - return api.client.stickyPost(form); + return api.client.featurePost(form); } export async function lockPost( diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index a6b1eef97..79d7942da 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2373,10 +2373,15 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lemmy-js-client@0.17.0-rc.48: - version "0.17.0-rc.48" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.48.tgz#6085812d4901b7d12b3fca237d8aced7f5210eac" - integrity sha512-Lz8Nzq/kczQtDj6STlbhxoEarFHtTCoWcWBabyPs6X6em/pfK/cnZqx1mMn7EaBSDUVQ+WL8UNFjQiqjhR4kww== +lemmy-js-client@0.17.0-rc.56: + version "0.17.0-rc.56" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.56.tgz#2c7abba9b8195826eb36401e7c5c2cb75609bcf2" + integrity sha512-7MM5xV8H9fIr1TbM/4e9PFKJpwlD2t135pSiH92TFgdkTzOMf0mtLO2BWLAQ7Rq+XVoVgj/WSBR4BofJka8XRQ== + dependencies: + "@sniptt/monads" "^0.5.10" + class-transformer "^0.5.1" + node-fetch "2.6.6" + reflect-metadata "^0.1.13" leven@^3.1.0: version "3.1.0" @@ -2511,6 +2516,13 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-fetch@2.6.6: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^2.6.1: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" diff --git a/crates/api/src/post/sticky.rs b/crates/api/src/post/feature.rs similarity index 56% rename from crates/api/src/post/sticky.rs rename to crates/api/src/post/feature.rs index 3a8aee7ab..16ba7bfb2 100644 --- a/crates/api/src/post/sticky.rs +++ b/crates/api/src/post/feature.rs @@ -2,26 +2,28 @@ use crate::Perform; use actix_web::web::Data; use lemmy_api_common::{ context::LemmyContext, - post::{PostResponse, StickyPost}, + post::{FeaturePost, PostResponse}, utils::{ check_community_ban, check_community_deleted_or_removed, get_local_user_view_from_jwt, + is_admin, is_mod_or_admin, }, websocket::{send::send_post_ws_message, UserOperation}, }; use lemmy_db_schema::{ source::{ - moderator::{ModStickyPost, ModStickyPostForm}, + moderator::{ModFeaturePost, ModFeaturePostForm}, post::{Post, PostUpdateForm}, }, traits::Crud, + PostFeatureType, }; use lemmy_utils::{error::LemmyError, ConnectionId}; #[async_trait::async_trait(?Send)] -impl Perform for StickyPost { +impl Perform for FeaturePost { type Response = PostResponse; #[tracing::instrument(skip(context, websocket_id))] @@ -30,7 +32,7 @@ impl Perform for StickyPost { context: &Data, websocket_id: Option, ) -> Result { - let data: &StickyPost = self; + let data: &FeaturePost = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; @@ -45,36 +47,44 @@ impl Perform for StickyPost { .await?; check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?; - // Verify that only the mods can sticky - is_mod_or_admin( - context.pool(), - local_user_view.person.id, - orig_post.community_id, - ) - .await?; + if data.feature_type == PostFeatureType::Community { + // Verify that only the mods can feature in community + is_mod_or_admin( + context.pool(), + local_user_view.person.id, + orig_post.community_id, + ) + .await?; + } else { + is_admin(&local_user_view)?; + } // Update the post let post_id = data.post_id; - let stickied = data.stickied; - Post::update( - context.pool(), - post_id, - &PostUpdateForm::builder().stickied(Some(stickied)).build(), - ) - .await?; + let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community { + PostUpdateForm::builder() + .featured_community(Some(data.featured)) + .build() + } else { + PostUpdateForm::builder() + .featured_local(Some(data.featured)) + .build() + }; + Post::update(context.pool(), post_id, &new_post).await?; // Mod tables - let form = ModStickyPostForm { + let form = ModFeaturePostForm { mod_person_id: local_user_view.person.id, post_id: data.post_id, - stickied: Some(stickied), + featured: data.featured, + is_featured_community: data.feature_type == PostFeatureType::Community, }; - ModStickyPost::create(context.pool(), &form).await?; + ModFeaturePost::create(context.pool(), &form).await?; send_post_ws_message( data.post_id, - UserOperation::StickyPost, + UserOperation::FeaturePost, websocket_id, Some(local_user_view.person.id), context, diff --git a/crates/api/src/post/mod.rs b/crates/api/src/post/mod.rs index 40cea54a2..4d8251356 100644 --- a/crates/api/src/post/mod.rs +++ b/crates/api/src/post/mod.rs @@ -1,6 +1,6 @@ +mod feature; mod get_link_metadata; mod like; mod lock; mod mark_read; mod save; -mod sticky; diff --git a/crates/api/src/site/mod_log.rs b/crates/api/src/site/mod_log.rs index 5e1c71a70..ec3ee0f70 100644 --- a/crates/api/src/site/mod_log.rs +++ b/crates/api/src/site/mod_log.rs @@ -19,12 +19,12 @@ use lemmy_db_views_moderator::structs::{ ModAddView, ModBanFromCommunityView, ModBanView, + ModFeaturePostView, ModHideCommunityView, ModLockPostView, ModRemoveCommentView, ModRemoveCommunityView, ModRemovePostView, - ModStickyPostView, ModTransferCommunityView, ModlogListParams, }; @@ -91,8 +91,8 @@ impl Perform for GetModlog { _ => Default::default(), }; - let stickied_posts = match type_ { - All | ModStickyPost => ModStickyPostView::list(context.pool(), params).await?, + let featured_posts = match type_ { + All | ModFeaturePost => ModFeaturePostView::list(context.pool(), params).await?, _ => Default::default(), }; @@ -181,7 +181,7 @@ impl Perform for GetModlog { Ok(GetModlogResponse { removed_posts, locked_posts, - stickied_posts, + featured_posts, removed_comments, removed_communities, banned_from_community, diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 4f486e059..82a1d6649 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -2,6 +2,7 @@ use crate::sensitive::Sensitive; use lemmy_db_schema::{ newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId}, ListingType, + PostFeatureType, SortType, }; use lemmy_db_views::structs::{PostReportView, PostView}; @@ -106,9 +107,10 @@ pub struct LockPost { } #[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub struct StickyPost { +pub struct FeaturePost { pub post_id: PostId, - pub stickied: bool, + pub featured: bool, + pub feature_type: PostFeatureType, pub auth: Sensitive, } diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 94e0ce7e7..c3f321ce7 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -31,12 +31,12 @@ use lemmy_db_views_moderator::structs::{ ModAddView, ModBanFromCommunityView, ModBanView, + ModFeaturePostView, ModHideCommunityView, ModLockPostView, ModRemoveCommentView, ModRemoveCommunityView, ModRemovePostView, - ModStickyPostView, ModTransferCommunityView, }; use serde::{Deserialize, Serialize}; @@ -93,7 +93,7 @@ pub struct GetModlog { pub struct GetModlogResponse { pub removed_posts: Vec, pub locked_posts: Vec, - pub stickied_posts: Vec, + pub featured_posts: Vec, pub removed_comments: Vec, pub removed_communities: Vec, pub banned_from_community: Vec, diff --git a/crates/api_common/src/websocket/mod.rs b/crates/api_common/src/websocket/mod.rs index 8686b8e4f..2af6cff06 100644 --- a/crates/api_common/src/websocket/mod.rs +++ b/crates/api_common/src/websocket/mod.rs @@ -38,7 +38,7 @@ pub enum UserOperation { ListCommentReports, CreatePostLike, LockPost, - StickyPost, + FeaturePost, MarkPostAsRead, SavePost, CreatePostReport, diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index 92cc70877..69eae583c 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -25,7 +25,7 @@ use activitypub_federation::{ use activitystreams_kinds::public; use lemmy_api_common::{ context::LemmyContext, - post::{CreatePost, EditPost, LockPost, PostResponse, StickyPost}, + post::{CreatePost, EditPost, FeaturePost, LockPost, PostResponse}, utils::get_local_user_view_from_jwt, websocket::{send::send_post_ws_message, UserOperationCrud}, }; @@ -101,7 +101,7 @@ impl SendActivity for LockPost { } #[async_trait::async_trait(?Send)] -impl SendActivity for StickyPost { +impl SendActivity for FeaturePost { type Response = PostResponse; async fn send_activity( @@ -205,9 +205,9 @@ impl ActivityHandler for CreateOrUpdatePage { // However, when fetching a remote post we generate a new create activity with the current // locked/stickied value, so this check may fail. So only check if its a local community, // because then we will definitely receive all create and update activities separately. - let is_stickied_or_locked = + let is_featured_or_locked = self.object.stickied == Some(true) || self.object.comments_enabled == Some(false); - if community.local && is_stickied_or_locked { + if community.local && is_featured_or_locked { return Err(LemmyError::from_message( "New post cannot be stickied or locked", )); diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index ecad2e464..e15e1b2dc 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ source::{ community::Community, local_site::LocalSite, - moderator::{ModLockPost, ModLockPostForm, ModStickyPost, ModStickyPostForm}, + moderator::{ModFeaturePost, ModFeaturePostForm, ModLockPost, ModLockPostForm}, person::Person, post::{Post, PostInsertForm, PostUpdateForm}, }, @@ -116,7 +116,7 @@ impl ApubObject for ApubPost { image: self.thumbnail_url.clone().map(ImageObject::new), comments_enabled: Some(!self.locked), sensitive: Some(self.nsfw), - stickied: Some(self.stickied), + stickied: Some(self.featured_community), language, published: Some(convert_datetime(self.published)), updated: self.updated.map(convert_datetime), @@ -208,7 +208,6 @@ impl ApubObject for ApubPost { updated: page.updated.map(|u| u.naive_local()), deleted: Some(false), nsfw: page.sensitive, - stickied: page.stickied, embed_title, embed_description, embed_video_url, @@ -216,6 +215,8 @@ impl ApubObject for ApubPost { ap_id: Some(page.id.clone().into()), local: Some(false), language_id, + featured_community: page.stickied, + featured_local: None, } } else { // if is mod action, only update locked/stickied fields, nothing else @@ -225,7 +226,7 @@ impl ApubObject for ApubPost { .community_id(community.id) .ap_id(Some(page.id.clone().into())) .locked(page.comments_enabled.map(|e| !e)) - .stickied(page.stickied) + .featured_community(page.stickied) .updated(page.updated.map(|u| u.naive_local())) .build() }; @@ -236,14 +237,15 @@ impl ApubObject for ApubPost { let post = Post::create(context.pool(), &form).await?; - // write mod log entries for sticky/lock - if Page::is_stickied_changed(&old_post, &page.stickied) { - let form = ModStickyPostForm { + // write mod log entries for feature/lock + if Page::is_featured_changed(&old_post, &page.stickied) { + let form = ModFeaturePostForm { mod_person_id: creator.id, post_id: post.id, - stickied: Some(post.stickied), + featured: post.featured_community, + is_featured_community: true, }; - ModStickyPost::create(context.pool(), &form).await?; + ModFeaturePost::create(context.pool(), &form).await?; } if Page::is_locked_changed(&old_post, &page.comments_enabled) { let form = ModLockPostForm { @@ -295,7 +297,7 @@ mod tests { assert!(post.body.is_some()); assert_eq!(post.body.as_ref().unwrap().len(), 45); assert!(!post.locked); - assert!(post.stickied); + assert!(post.featured_community); assert_eq!(request_counter, 0); Post::delete(context.pool(), post.id).await.unwrap(); diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 65df30e42..3aadb20c1 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -137,18 +137,18 @@ impl Page { .dereference_local(context) .await; - let stickied_changed = Page::is_stickied_changed(&old_post, &self.stickied); + let featured_changed = Page::is_featured_changed(&old_post, &self.stickied); let locked_changed = Page::is_locked_changed(&old_post, &self.comments_enabled); - Ok(stickied_changed || locked_changed) + Ok(featured_changed || locked_changed) } - pub(crate) fn is_stickied_changed( + pub(crate) fn is_featured_changed( old_post: &Result, - new_stickied: &Option, + new_featured_community: &Option, ) -> bool { - if let Some(new_stickied) = new_stickied { + if let Some(new_featured_community) = new_featured_community { if let Ok(old_post) = old_post { - return new_stickied != &old_post.stickied; + return new_featured_community != &old_post.featured_community; } } diff --git a/crates/db_schema/src/aggregates/structs.rs b/crates/db_schema/src/aggregates/structs.rs index 08d5e9144..d52b5bb41 100644 --- a/crates/db_schema/src/aggregates/structs.rs +++ b/crates/db_schema/src/aggregates/structs.rs @@ -68,10 +68,11 @@ pub struct PostAggregates { pub score: i64, pub upvotes: i64, pub downvotes: i64, - pub stickied: bool, pub published: chrono::NaiveDateTime, pub newest_comment_time_necro: chrono::NaiveDateTime, // A newest comment time, limited to 2 days, to prevent necrobumping pub newest_comment_time: chrono::NaiveDateTime, + pub featured_community: bool, + pub featured_local: bool, } #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index fc9b48038..23be96146 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -16,6 +16,8 @@ use crate::{ ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm, + ModFeaturePost, + ModFeaturePostForm, ModHideCommunity, ModHideCommunityForm, ModLockPost, @@ -26,8 +28,6 @@ use crate::{ ModRemoveCommunityForm, ModRemovePost, ModRemovePostForm, - ModStickyPost, - ModStickyPostForm, ModTransferCommunity, ModTransferCommunityForm, }, @@ -98,29 +98,29 @@ impl Crud for ModLockPost { } #[async_trait] -impl Crud for ModStickyPost { - type InsertForm = ModStickyPostForm; - type UpdateForm = ModStickyPostForm; +impl Crud for ModFeaturePost { + type InsertForm = ModFeaturePostForm; + type UpdateForm = ModFeaturePostForm; type IdType = i32; async fn read(pool: &DbPool, from_id: i32) -> Result { - use crate::schema::mod_sticky_post::dsl::mod_sticky_post; + use crate::schema::mod_feature_post::dsl::mod_feature_post; let conn = &mut get_conn(pool).await?; - mod_sticky_post.find(from_id).first::(conn).await + mod_feature_post.find(from_id).first::(conn).await } - async fn create(pool: &DbPool, form: &ModStickyPostForm) -> Result { - use crate::schema::mod_sticky_post::dsl::mod_sticky_post; + async fn create(pool: &DbPool, form: &ModFeaturePostForm) -> Result { + use crate::schema::mod_feature_post::dsl::mod_feature_post; let conn = &mut get_conn(pool).await?; - insert_into(mod_sticky_post) + insert_into(mod_feature_post) .values(form) .get_result::(conn) .await } - async fn update(pool: &DbPool, from_id: i32, form: &ModStickyPostForm) -> Result { - use crate::schema::mod_sticky_post::dsl::mod_sticky_post; + async fn update(pool: &DbPool, from_id: i32, form: &ModFeaturePostForm) -> Result { + use crate::schema::mod_feature_post::dsl::mod_feature_post; let conn = &mut get_conn(pool).await?; - diesel::update(mod_sticky_post.find(from_id)) + diesel::update(mod_feature_post.find(from_id)) .set(form) .get_result::(conn) .await @@ -525,6 +525,8 @@ mod tests { ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm, + ModFeaturePost, + ModFeaturePostForm, ModLockPost, ModLockPostForm, ModRemoveComment, @@ -533,8 +535,6 @@ mod tests { ModRemoveCommunityForm, ModRemovePost, ModRemovePostForm, - ModStickyPost, - ModStickyPostForm, }, person::{Person, PersonInsertForm}, post::{Post, PostInsertForm}, @@ -637,25 +637,27 @@ mod tests { when_: inserted_mod_lock_post.when_, }; - // sticky post + // feature post - let mod_sticky_post_form = ModStickyPostForm { + let mod_feature_post_form = ModFeaturePostForm { mod_person_id: inserted_mod.id, post_id: inserted_post.id, - stickied: None, + featured: false, + is_featured_community: true, }; - let inserted_mod_sticky_post = ModStickyPost::create(pool, &mod_sticky_post_form) + let inserted_mod_feature_post = ModFeaturePost::create(pool, &mod_feature_post_form) .await .unwrap(); - let read_mod_sticky_post = ModStickyPost::read(pool, inserted_mod_sticky_post.id) + let read_mod_feature_post = ModFeaturePost::read(pool, inserted_mod_feature_post.id) .await .unwrap(); - let expected_mod_sticky_post = ModStickyPost { - id: inserted_mod_sticky_post.id, + let expected_mod_feature_post = ModFeaturePost { + id: inserted_mod_feature_post.id, post_id: inserted_post.id, mod_person_id: inserted_mod.id, - stickied: Some(true), - when_: inserted_mod_sticky_post.when_, + featured: false, + is_featured_community: true, + when_: inserted_mod_feature_post.when_, }; // comment @@ -809,7 +811,7 @@ mod tests { assert_eq!(expected_mod_remove_post, read_mod_remove_post); assert_eq!(expected_mod_lock_post, read_mod_lock_post); - assert_eq!(expected_mod_sticky_post, read_mod_sticky_post); + assert_eq!(expected_mod_feature_post, read_mod_feature_post); assert_eq!(expected_mod_remove_comment, read_mod_remove_comment); assert_eq!(expected_mod_remove_community, read_mod_remove_community); assert_eq!(expected_mod_ban_from_community, read_mod_ban_from_community); diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 407a83f53..2a5264402 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -6,11 +6,11 @@ use crate::{ community_id, creator_id, deleted, + featured_community, name, post, published, removed, - stickied, thumbnail_url, updated, url, @@ -83,7 +83,7 @@ impl Post { .filter(deleted.eq(false)) .filter(removed.eq(false)) .then_order_by(published.desc()) - .then_order_by(stickied.desc()) + .then_order_by(featured_community.desc()) .limit(FETCH_LIMIT_MAX) .load::(conn) .await @@ -381,7 +381,6 @@ mod tests { published: inserted_post.published, removed: false, locked: false, - stickied: false, nsfw: false, deleted: false, updated: None, @@ -392,6 +391,8 @@ mod tests { ap_id: inserted_post.ap_id.clone(), local: true, language_id: Default::default(), + featured_community: false, + featured_local: false, }; // Post Like diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 4118b1eb4..b0e56393f 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -82,7 +82,7 @@ pub enum ModlogActionType { All, ModRemovePost, ModLockPost, - ModStickyPost, + ModFeaturePost, ModRemoveComment, ModRemoveCommunity, ModBanFromCommunity, @@ -96,3 +96,12 @@ pub enum ModlogActionType { AdminPurgePost, AdminPurgeComment, } + +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, +)] +pub enum PostFeatureType { + #[default] + Local, + Community, +} diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index f6e5aad76..97def1ffa 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -273,12 +273,13 @@ table! { } table! { - mod_sticky_post (id) { + mod_feature_post (id) { id -> Int4, mod_person_id -> Int4, post_id -> Int4, - stickied -> Nullable, + featured -> Bool, when_ -> Timestamp, + is_featured_community -> Bool, } } @@ -371,7 +372,6 @@ table! { updated -> Nullable, deleted -> Bool, nsfw -> Bool, - stickied -> Bool, embed_title -> Nullable, embed_description -> Nullable, embed_video_url -> Nullable, @@ -379,6 +379,8 @@ table! { ap_id -> Varchar, local -> Bool, language_id -> Int4, + featured_community -> Bool, + featured_local -> Bool, } } @@ -400,10 +402,11 @@ table! { score -> Int8, upvotes -> Int8, downvotes -> Int8, - stickied -> Bool, published -> Timestamp, newest_comment_time_necro -> Timestamp, newest_comment_time -> Timestamp, + featured_community -> Bool, + featured_local -> Bool, } } @@ -771,8 +774,8 @@ joinable!(mod_remove_community -> community (community_id)); joinable!(mod_remove_community -> person (mod_person_id)); joinable!(mod_remove_post -> person (mod_person_id)); joinable!(mod_remove_post -> post (post_id)); -joinable!(mod_sticky_post -> person (mod_person_id)); -joinable!(mod_sticky_post -> post (post_id)); +joinable!(mod_feature_post -> person (mod_person_id)); +joinable!(mod_feature_post -> post (post_id)); joinable!(password_reset_request -> local_user (local_user_id)); joinable!(person_aggregates -> person (person_id)); joinable!(person_ban -> person (person_id)); @@ -848,7 +851,7 @@ allow_tables_to_appear_in_same_query!( mod_remove_comment, mod_remove_community, mod_remove_post, - mod_sticky_post, + mod_feature_post, mod_hide_community, password_reset_request, person, diff --git a/crates/db_schema/src/source/moderator.rs b/crates/db_schema/src/source/moderator.rs index 000e34868..5d9bda273 100644 --- a/crates/db_schema/src/source/moderator.rs +++ b/crates/db_schema/src/source/moderator.rs @@ -9,12 +9,12 @@ use crate::schema::{ mod_add_community, mod_ban, mod_ban_from_community, + mod_feature_post, mod_hide_community, mod_lock_post, mod_remove_comment, mod_remove_community, mod_remove_post, - mod_sticky_post, mod_transfer_community, }; use serde::{Deserialize, Serialize}; @@ -61,21 +61,23 @@ pub struct ModLockPostForm { #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "full", derive(Queryable, Identifiable))] -#[cfg_attr(feature = "full", diesel(table_name = mod_sticky_post))] -pub struct ModStickyPost { +#[cfg_attr(feature = "full", diesel(table_name = mod_feature_post))] +pub struct ModFeaturePost { pub id: i32, pub mod_person_id: PersonId, pub post_id: PostId, - pub stickied: Option, + pub featured: bool, pub when_: chrono::NaiveDateTime, + pub is_featured_community: bool, } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = mod_sticky_post))] -pub struct ModStickyPostForm { +#[cfg_attr(feature = "full", diesel(table_name = mod_feature_post))] +pub struct ModFeaturePostForm { pub mod_person_id: PersonId, pub post_id: PostId, - pub stickied: Option, + pub featured: bool, + pub is_featured_community: bool, } #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index df3116ef3..0b14b3f49 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -20,7 +20,6 @@ pub struct Post { pub updated: Option, pub deleted: bool, pub nsfw: bool, - pub stickied: bool, pub embed_title: Option, pub embed_description: Option, pub embed_video_url: Option, @@ -28,6 +27,8 @@ pub struct Post { pub ap_id: DbUrl, pub local: bool, pub language_id: LanguageId, + pub featured_community: bool, + pub featured_local: bool, } #[derive(Debug, Clone, TypedBuilder)] @@ -49,7 +50,6 @@ pub struct PostInsertForm { pub updated: Option, pub published: Option, pub deleted: Option, - pub stickied: Option, pub embed_title: Option, pub embed_description: Option, pub embed_video_url: Option, @@ -57,6 +57,8 @@ pub struct PostInsertForm { pub ap_id: Option, pub local: Option, pub language_id: Option, + pub featured_community: Option, + pub featured_local: Option, } #[derive(Debug, Clone, TypedBuilder)] @@ -73,7 +75,6 @@ pub struct PostUpdateForm { pub published: Option, pub updated: Option>, pub deleted: Option, - pub stickied: Option, pub embed_title: Option>, pub embed_description: Option>, pub embed_video_url: Option>, @@ -81,6 +82,8 @@ pub struct PostUpdateForm { pub ap_id: Option, pub local: Option, pub language_id: Option, + pub featured_community: Option, + pub featured_local: Option, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 051f19835..66daf831c 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -863,7 +863,6 @@ mod tests { removed: false, deleted: false, locked: false, - stickied: false, nsfw: false, embed_title: None, embed_description: None, @@ -872,6 +871,8 @@ mod tests { ap_id: data.inserted_post.ap_id.clone(), local: true, language_id: Default::default(), + featured_community: false, + featured_local: false, }, community: CommunitySafe { id: data.inserted_community.id, diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 40b90d67f..523a19feb 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -469,10 +469,11 @@ mod tests { score: 0, upvotes: 0, downvotes: 0, - stickied: false, published: agg.published, newest_comment_time_necro: inserted_post.published, newest_comment_time: inserted_post.published, + featured_community: false, + featured_local: false, }, resolver: None, }; diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index f5c8100fd..4a36b816f 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -324,17 +324,16 @@ impl<'a> PostQuery<'a> { } } } - - if let Some(community_id) = self.community_id { + if self.community_id.is_none() && self.community_actor_id.is_none() { + query = query.then_order_by(post_aggregates::featured_local.desc()); + } else if let Some(community_id) = self.community_id { query = query .filter(post::community_id.eq(community_id)) - .then_order_by(post_aggregates::stickied.desc()); - } - - if let Some(community_actor_id) = self.community_actor_id { + .then_order_by(post_aggregates::featured_community.desc()); + } else if let Some(community_actor_id) = self.community_actor_id { query = query .filter(community::actor_id.eq(community_actor_id)) - .then_order_by(post_aggregates::stickied.desc()); + .then_order_by(post_aggregates::featured_community.desc()); } if let Some(url_search) = self.url_search { @@ -860,7 +859,6 @@ mod tests { removed: false, deleted: false, locked: false, - stickied: false, nsfw: false, embed_title: None, embed_description: None, @@ -869,6 +867,8 @@ mod tests { ap_id: inserted_post.ap_id.clone(), local: true, language_id: LanguageId(47), + featured_community: false, + featured_local: false, }, my_vote: None, unread_comments: 0, @@ -919,10 +919,11 @@ mod tests { score: 0, upvotes: 0, downvotes: 0, - stickied: false, published: agg.published, newest_comment_time_necro: inserted_post.published, newest_comment_time: inserted_post.published, + featured_community: false, + featured_local: false, }, subscribed: SubscribedType::NotSubscribed, read: false, diff --git a/crates/db_views_moderator/src/lib.rs b/crates/db_views_moderator/src/lib.rs index 60d5c7877..d3e7efffd 100644 --- a/crates/db_views_moderator/src/lib.rs +++ b/crates/db_views_moderator/src/lib.rs @@ -15,6 +15,8 @@ pub mod mod_ban_from_community_view; #[cfg(feature = "full")] pub mod mod_ban_view; #[cfg(feature = "full")] +pub mod mod_feature_post_view; +#[cfg(feature = "full")] pub mod mod_hide_community_view; #[cfg(feature = "full")] pub mod mod_lock_post_view; @@ -25,7 +27,5 @@ pub mod mod_remove_community_view; #[cfg(feature = "full")] pub mod mod_remove_post_view; #[cfg(feature = "full")] -pub mod mod_sticky_post_view; -#[cfg(feature = "full")] pub mod mod_transfer_community_view; pub mod structs; diff --git a/crates/db_views_moderator/src/mod_sticky_post_view.rs b/crates/db_views_moderator/src/mod_feature_post_view.rs similarity index 75% rename from crates/db_views_moderator/src/mod_sticky_post_view.rs rename to crates/db_views_moderator/src/mod_feature_post_view.rs index 287362924..852746de6 100644 --- a/crates/db_views_moderator/src/mod_sticky_post_view.rs +++ b/crates/db_views_moderator/src/mod_feature_post_view.rs @@ -1,4 +1,4 @@ -use crate::structs::{ModStickyPostView, ModlogListParams}; +use crate::structs::{ModFeaturePostView, ModlogListParams}; use diesel::{ result::Error, BoolExpressionMethods, @@ -11,10 +11,10 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, - schema::{community, mod_sticky_post, person, post}, + schema::{community, mod_feature_post, person, post}, source::{ community::{Community, CommunitySafe}, - moderator::ModStickyPost, + moderator::ModFeaturePost, person::{Person, PersonSafe}, post::Post, }, @@ -22,9 +22,9 @@ use lemmy_db_schema::{ utils::{get_conn, limit_and_offset, DbPool}, }; -type ModStickyPostViewTuple = (ModStickyPost, Option, Post, CommunitySafe); +type ModFeaturePostViewTuple = (ModFeaturePost, Option, Post, CommunitySafe); -impl ModStickyPostView { +impl ModFeaturePostView { pub async fn list(pool: &DbPool, params: ModlogListParams) -> Result, Error> { let conn = &mut get_conn(pool).await?; let person_alias_1 = diesel::alias!(person as person1); @@ -32,16 +32,16 @@ impl ModStickyPostView { let show_mod_names = !params.hide_modlog_names; let show_mod_names_expr = show_mod_names.as_sql::(); - let admin_names_join = mod_sticky_post::mod_person_id + let admin_names_join = mod_feature_post::mod_person_id .eq(person::id) .and(show_mod_names_expr.or(person::id.eq(admin_person_id_join))); - let mut query = mod_sticky_post::table + let mut query = mod_feature_post::table .left_join(person::table.on(admin_names_join)) .inner_join(post::table) .inner_join(person_alias_1.on(post::creator_id.eq(person_alias_1.field(person::id)))) .inner_join(community::table.on(post::community_id.eq(community::id))) .select(( - mod_sticky_post::all_columns, + mod_feature_post::all_columns, Person::safe_columns_tuple().nullable(), post::all_columns, Community::safe_columns_tuple(), @@ -53,7 +53,7 @@ impl ModStickyPostView { }; if let Some(mod_person_id) = params.mod_person_id { - query = query.filter(mod_sticky_post::mod_person_id.eq(mod_person_id)); + query = query.filter(mod_feature_post::mod_person_id.eq(mod_person_id)); }; if let Some(other_person_id) = params.other_person_id { @@ -65,8 +65,8 @@ impl ModStickyPostView { let res = query .limit(limit) .offset(offset) - .order_by(mod_sticky_post::when_.desc()) - .load::(conn) + .order_by(mod_feature_post::when_.desc()) + .load::(conn) .await?; let results = Self::from_tuple_to_vec(res); @@ -74,13 +74,13 @@ impl ModStickyPostView { } } -impl ViewToVec for ModStickyPostView { - type DbTuple = ModStickyPostViewTuple; +impl ViewToVec for ModFeaturePostView { + type DbTuple = ModFeaturePostViewTuple; fn from_tuple_to_vec(items: Vec) -> Vec { items .into_iter() .map(|a| Self { - mod_sticky_post: a.0, + mod_feature_post: a.0, moderator: a.1, post: a.2, community: a.3, diff --git a/crates/db_views_moderator/src/structs.rs b/crates/db_views_moderator/src/structs.rs index 863de0704..85dc006b1 100644 --- a/crates/db_views_moderator/src/structs.rs +++ b/crates/db_views_moderator/src/structs.rs @@ -12,12 +12,12 @@ use lemmy_db_schema::{ ModAddCommunity, ModBan, ModBanFromCommunity, + ModFeaturePost, ModHideCommunity, ModLockPost, ModRemoveComment, ModRemoveCommunity, ModRemovePost, - ModStickyPost, ModTransferCommunity, }, person::PersonSafe, @@ -97,8 +97,8 @@ pub struct ModRemovePostView { } #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ModStickyPostView { - pub mod_sticky_post: ModStickyPost, +pub struct ModFeaturePostView { + pub mod_feature_post: ModFeaturePost, pub moderator: Option, pub post: Post, pub community: CommunitySafe, diff --git a/migrations/2022-11-20-032430_sticky_local/down.sql b/migrations/2022-11-20-032430_sticky_local/down.sql new file mode 100644 index 000000000..46a2b6a9b --- /dev/null +++ b/migrations/2022-11-20-032430_sticky_local/down.sql @@ -0,0 +1,47 @@ + +DROP TRIGGER IF EXISTS post_aggregates_featured_local ON post; +DROP TRIGGER IF EXISTS post_aggregates_featured_community ON post; +drop function post_aggregates_featured_community; +drop function post_aggregates_featured_local; + + +alter table post ADD stickied boolean NOT NULL DEFAULT false; +Update post +set stickied = featured_community; +alter table post DROP COLUMN featured_community; +alter table post DROP COLUMN featured_local; + +alter table post_aggregates ADD stickied boolean NOT NULL DEFAULT false; +Update post_aggregates +set stickied = featured_community; +alter table post_aggregates DROP COLUMN featured_community; +alter table post_aggregates DROP COLUMN featured_local; + +alter table mod_feature_post +rename column featured TO stickied; + +alter table mod_feature_post +DROP COLUMN is_featured_community; + +alter table mod_feature_post +alter column stickied DROP NOT NULL; + +alter table mod_feature_post +Rename To mod_sticky_post; + +create function post_aggregates_stickied() +returns trigger language plpgsql +as $$ +begin + update post_aggregates pa + set stickied = NEW.stickied + where pa.post_id = NEW.id; + + return null; +end $$; + +create trigger post_aggregates_stickied +after update on post +for each row +when (OLD.stickied is distinct from NEW.stickied) +execute procedure post_aggregates_stickied(); \ No newline at end of file diff --git a/migrations/2022-11-20-032430_sticky_local/up.sql b/migrations/2022-11-20-032430_sticky_local/up.sql new file mode 100644 index 000000000..202c4c4b0 --- /dev/null +++ b/migrations/2022-11-20-032430_sticky_local/up.sql @@ -0,0 +1,63 @@ + +DROP TRIGGER IF EXISTS post_aggregates_stickied ON post; +drop function + post_aggregates_stickied; + + +alter table post ADD featured_community boolean NOT NULL DEFAULT false; +alter table post ADD featured_local boolean NOT NULL DEFAULT false; +update post +set featured_community = stickied; +alter table post DROP COLUMN stickied; + +alter table post_aggregates ADD featured_community boolean NOT NULL DEFAULT false; +alter table post_aggregates ADD featured_local boolean NOT NULL DEFAULT false; +update post_aggregates +set featured_community = stickied; +alter table post_aggregates DROP COLUMN stickied; + +alter table mod_sticky_post +rename column stickied TO featured; + +alter table mod_sticky_post +alter column featured SET NOT NULL; + +alter table mod_sticky_post +ADD is_featured_community boolean NOT NULL DEFAULT true; + +alter table mod_sticky_post +Rename To mod_feature_post; + +create function post_aggregates_featured_community() +returns trigger language plpgsql +as $$ +begin + update post_aggregates pa + set featured_community = NEW.featured_community + where pa.post_id = NEW.id; + return null; +end $$; + +create function post_aggregates_featured_local() +returns trigger language plpgsql +as $$ +begin + update post_aggregates pa + set featured_local = NEW.featured_local + where pa.post_id = NEW.id; + return null; +end $$; + +CREATE TRIGGER post_aggregates_featured_community + AFTER UPDATE + ON public.post + FOR EACH ROW + WHEN (old.featured_community IS DISTINCT FROM new.featured_community) + EXECUTE FUNCTION public.post_aggregates_featured_community(); + +CREATE TRIGGER post_aggregates_featured_local + AFTER UPDATE + ON public.post + FOR EACH ROW + WHEN (old.featured_local IS DISTINCT FROM new.featured_local) + EXECUTE FUNCTION public.post_aggregates_featured_local(); \ No newline at end of file diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 34083a48d..ce7ce68bb 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -59,6 +59,7 @@ use lemmy_api_common::{ CreatePostReport, DeletePost, EditPost, + FeaturePost, GetPost, GetPosts, GetSiteMetadata, @@ -68,7 +69,6 @@ use lemmy_api_common::{ RemovePost, ResolvePostReport, SavePost, - StickyPost, }, private_message::{ CreatePrivateMessage, @@ -183,7 +183,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { web::post().to(route_post::), ) .route("/lock", web::post().to(route_post::)) - .route("/sticky", web::post().to(route_post::)) + .route("/feature", web::post().to(route_post::)) .route("/list", web::get().to(route_get_apub::)) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) diff --git a/src/api_routes_websocket.rs b/src/api_routes_websocket.rs index 7a8656005..b25818382 100644 --- a/src/api_routes_websocket.rs +++ b/src/api_routes_websocket.rs @@ -60,6 +60,7 @@ use lemmy_api_common::{ CreatePostReport, DeletePost, EditPost, + FeaturePost, GetPost, GetPosts, GetSiteMetadata, @@ -69,7 +70,6 @@ use lemmy_api_common::{ RemovePost, ResolvePostReport, SavePost, - StickyPost, }, private_message::{ CreatePrivateMessage, @@ -560,7 +560,9 @@ pub async fn match_websocket_operation( // Post ops UserOperation::LockPost => do_websocket_operation::(context, id, op, data).await, - UserOperation::StickyPost => do_websocket_operation::(context, id, op, data).await, + UserOperation::FeaturePost => { + do_websocket_operation::(context, id, op, data).await + } UserOperation::CreatePostLike => { do_websocket_operation::(context, id, op, data).await }