Add support for Featured Posts (#2585)

* Add support for Featured Posts

* Fix rebase

* More fixes
This commit is contained in:
Anon 2022-12-12 05:17:10 -06:00 committed by GitHub
parent 0ecf256ce3
commit 9dfd819691
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 319 additions and 156 deletions

View file

@ -20,7 +20,7 @@
"eslint": "^8.25.0", "eslint": "^8.25.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"jest": "^27.0.6", "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", "node-fetch": "^2.6.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",

View file

@ -11,7 +11,7 @@ import {
setupLogins, setupLogins,
createPost, createPost,
editPost, editPost,
stickyPost, featurePost,
lockPost, lockPost,
resolvePost, resolvePost,
likePost, likePost,
@ -157,8 +157,8 @@ test("Sticky a post", async () => {
let betaPost1 = ( let betaPost1 = (
await resolvePost(beta, postRes.post_view.post) await resolvePost(beta, postRes.post_view.post)
).post.unwrap(); ).post.unwrap();
let stickiedPostRes = await stickyPost(beta, true, betaPost1.post); let stickiedPostRes = await featurePost(beta, true, betaPost1.post);
expect(stickiedPostRes.post_view.post.stickied).toBe(true); expect(stickiedPostRes.post_view.post.featured_community).toBe(true);
// Make sure that post is stickied on beta // Make sure that post is stickied on beta
let betaPost = ( let betaPost = (
@ -166,11 +166,11 @@ test("Sticky a post", async () => {
).post.unwrap(); ).post.unwrap();
expect(betaPost.community.local).toBe(true); expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false); expect(betaPost.creator.local).toBe(false);
expect(betaPost.post.stickied).toBe(true); expect(betaPost.post.featured_community).toBe(true);
// Unsticky a post // Unsticky a post
let unstickiedPost = await stickyPost(beta, false, betaPost1.post); let unstickiedPost = await featurePost(beta, false, betaPost1.post);
expect(unstickiedPost.post_view.post.stickied).toBe(false); expect(unstickiedPost.post_view.post.featured_community).toBe(false);
// Make sure that post is unstickied on beta // Make sure that post is unstickied on beta
let betaPost2 = ( let betaPost2 = (
@ -178,18 +178,18 @@ test("Sticky a post", async () => {
).post.unwrap(); ).post.unwrap();
expect(betaPost2.community.local).toBe(true); expect(betaPost2.community.local).toBe(true);
expect(betaPost2.creator.local).toBe(false); 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 // Make sure that gamma cannot sticky the post on beta
let gammaPost = ( let gammaPost = (
await resolvePost(gamma, postRes.post_view.post) await resolvePost(gamma, postRes.post_view.post)
).post.unwrap(); ).post.unwrap();
let gammaTrySticky = await stickyPost(gamma, true, gammaPost.post); let gammaTrySticky = await featurePost(gamma, true, gammaPost.post);
let betaPost3 = ( let betaPost3 = (
await resolvePost(beta, postRes.post_view.post) await resolvePost(beta, postRes.post_view.post)
).post.unwrap(); ).post.unwrap();
expect(gammaTrySticky.post_view.post.stickied).toBe(true); expect(gammaTrySticky.post_view.post.featured_community).toBe(true);
expect(betaPost3.post.stickied).toBe(false); expect(betaPost3.post.featured_community).toBe(false);
}); });
test("Lock a post", async () => { test("Lock a post", async () => {

View file

@ -7,7 +7,6 @@ import {
CreateComment, CreateComment,
DeletePost, DeletePost,
RemovePost, RemovePost,
StickyPost,
LockPost, LockPost,
PostResponse, PostResponse,
SearchResponse, SearchResponse,
@ -64,6 +63,8 @@ import {
CommentSortType, CommentSortType,
GetComments, GetComments,
GetCommentsResponse, GetCommentsResponse,
FeaturePost,
PostFeatureType,
} from "lemmy-js-client"; } from "lemmy-js-client";
export interface API { export interface API {
@ -180,14 +181,13 @@ export async function setupLogins() {
rate_limit_search: Some(999), rate_limit_search: Some(999),
rate_limit_search_per_second: None, rate_limit_search_per_second: None,
federation_enabled: None, federation_enabled: None,
federation_strict_allowlist: None,
federation_http_fetch_retry_limit: None,
federation_worker_count: None, federation_worker_count: None,
captcha_enabled: None, captcha_enabled: None,
captcha_difficulty: None, captcha_difficulty: None,
allowed_instances: None, allowed_instances: None,
blocked_instances: None, blocked_instances: None,
auth: "", auth: "",
taglines: None,
}); });
// Set the blocks and auths for each // Set the blocks and auths for each
@ -293,17 +293,18 @@ export async function removePost(
return api.client.removePost(form); return api.client.removePost(form);
} }
export async function stickyPost( export async function featurePost(
api: API, api: API,
stickied: boolean, featured: boolean,
post: Post post: Post
): Promise<PostResponse> { ): Promise<PostResponse> {
let form = new StickyPost({ let form = new FeaturePost({
post_id: post.id, post_id: post.id,
stickied, featured,
feature_type: PostFeatureType.Community,
auth: api.auth.unwrap(), auth: api.auth.unwrap(),
}); });
return api.client.stickyPost(form); return api.client.featurePost(form);
} }
export async function lockPost( export async function lockPost(

View file

@ -2373,10 +2373,15 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lemmy-js-client@0.17.0-rc.48: lemmy-js-client@0.17.0-rc.56:
version "0.17.0-rc.48" version "0.17.0-rc.56"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.48.tgz#6085812d4901b7d12b3fca237d8aced7f5210eac" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.56.tgz#2c7abba9b8195826eb36401e7c5c2cb75609bcf2"
integrity sha512-Lz8Nzq/kczQtDj6STlbhxoEarFHtTCoWcWBabyPs6X6em/pfK/cnZqx1mMn7EaBSDUVQ+WL8UNFjQiqjhR4kww== 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: leven@^3.1.0:
version "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" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== 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: node-fetch@^2.6.1:
version "2.6.7" version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"

View file

@ -2,26 +2,28 @@ use crate::Perform;
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{PostResponse, StickyPost}, post::{FeaturePost, PostResponse},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
is_admin,
is_mod_or_admin, is_mod_or_admin,
}, },
websocket::{send::send_post_ws_message, UserOperation}, websocket::{send::send_post_ws_message, UserOperation},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
moderator::{ModStickyPost, ModStickyPostForm}, moderator::{ModFeaturePost, ModFeaturePostForm},
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
traits::Crud, traits::Crud,
PostFeatureType,
}; };
use lemmy_utils::{error::LemmyError, ConnectionId}; use lemmy_utils::{error::LemmyError, ConnectionId};
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for StickyPost { impl Perform for FeaturePost {
type Response = PostResponse; type Response = PostResponse;
#[tracing::instrument(skip(context, websocket_id))] #[tracing::instrument(skip(context, websocket_id))]
@ -30,7 +32,7 @@ impl Perform for StickyPost {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> { ) -> Result<PostResponse, LemmyError> {
let data: &StickyPost = self; let data: &FeaturePost = self;
let local_user_view = let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
@ -45,36 +47,44 @@ impl Perform for StickyPost {
.await?; .await?;
check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?; check_community_deleted_or_removed(orig_post.community_id, context.pool()).await?;
// Verify that only the mods can sticky if data.feature_type == PostFeatureType::Community {
is_mod_or_admin( // Verify that only the mods can feature in community
context.pool(), is_mod_or_admin(
local_user_view.person.id, context.pool(),
orig_post.community_id, local_user_view.person.id,
) orig_post.community_id,
.await?; )
.await?;
} else {
is_admin(&local_user_view)?;
}
// Update the post // Update the post
let post_id = data.post_id; let post_id = data.post_id;
let stickied = data.stickied; let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community {
Post::update( PostUpdateForm::builder()
context.pool(), .featured_community(Some(data.featured))
post_id, .build()
&PostUpdateForm::builder().stickied(Some(stickied)).build(), } else {
) PostUpdateForm::builder()
.await?; .featured_local(Some(data.featured))
.build()
};
Post::update(context.pool(), post_id, &new_post).await?;
// Mod tables // Mod tables
let form = ModStickyPostForm { let form = ModFeaturePostForm {
mod_person_id: local_user_view.person.id, mod_person_id: local_user_view.person.id,
post_id: data.post_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( send_post_ws_message(
data.post_id, data.post_id,
UserOperation::StickyPost, UserOperation::FeaturePost,
websocket_id, websocket_id,
Some(local_user_view.person.id), Some(local_user_view.person.id),
context, context,

View file

@ -1,6 +1,6 @@
mod feature;
mod get_link_metadata; mod get_link_metadata;
mod like; mod like;
mod lock; mod lock;
mod mark_read; mod mark_read;
mod save; mod save;
mod sticky;

View file

@ -19,12 +19,12 @@ use lemmy_db_views_moderator::structs::{
ModAddView, ModAddView,
ModBanFromCommunityView, ModBanFromCommunityView,
ModBanView, ModBanView,
ModFeaturePostView,
ModHideCommunityView, ModHideCommunityView,
ModLockPostView, ModLockPostView,
ModRemoveCommentView, ModRemoveCommentView,
ModRemoveCommunityView, ModRemoveCommunityView,
ModRemovePostView, ModRemovePostView,
ModStickyPostView,
ModTransferCommunityView, ModTransferCommunityView,
ModlogListParams, ModlogListParams,
}; };
@ -91,8 +91,8 @@ impl Perform for GetModlog {
_ => Default::default(), _ => Default::default(),
}; };
let stickied_posts = match type_ { let featured_posts = match type_ {
All | ModStickyPost => ModStickyPostView::list(context.pool(), params).await?, All | ModFeaturePost => ModFeaturePostView::list(context.pool(), params).await?,
_ => Default::default(), _ => Default::default(),
}; };
@ -181,7 +181,7 @@ impl Perform for GetModlog {
Ok(GetModlogResponse { Ok(GetModlogResponse {
removed_posts, removed_posts,
locked_posts, locked_posts,
stickied_posts, featured_posts,
removed_comments, removed_comments,
removed_communities, removed_communities,
banned_from_community, banned_from_community,

View file

@ -2,6 +2,7 @@ use crate::sensitive::Sensitive;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId}, newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
ListingType, ListingType,
PostFeatureType,
SortType, SortType,
}; };
use lemmy_db_views::structs::{PostReportView, PostView}; use lemmy_db_views::structs::{PostReportView, PostView};
@ -106,9 +107,10 @@ pub struct LockPost {
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct StickyPost { pub struct FeaturePost {
pub post_id: PostId, pub post_id: PostId,
pub stickied: bool, pub featured: bool,
pub feature_type: PostFeatureType,
pub auth: Sensitive<String>, pub auth: Sensitive<String>,
} }

View file

@ -31,12 +31,12 @@ use lemmy_db_views_moderator::structs::{
ModAddView, ModAddView,
ModBanFromCommunityView, ModBanFromCommunityView,
ModBanView, ModBanView,
ModFeaturePostView,
ModHideCommunityView, ModHideCommunityView,
ModLockPostView, ModLockPostView,
ModRemoveCommentView, ModRemoveCommentView,
ModRemoveCommunityView, ModRemoveCommunityView,
ModRemovePostView, ModRemovePostView,
ModStickyPostView,
ModTransferCommunityView, ModTransferCommunityView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -93,7 +93,7 @@ pub struct GetModlog {
pub struct GetModlogResponse { pub struct GetModlogResponse {
pub removed_posts: Vec<ModRemovePostView>, pub removed_posts: Vec<ModRemovePostView>,
pub locked_posts: Vec<ModLockPostView>, pub locked_posts: Vec<ModLockPostView>,
pub stickied_posts: Vec<ModStickyPostView>, pub featured_posts: Vec<ModFeaturePostView>,
pub removed_comments: Vec<ModRemoveCommentView>, pub removed_comments: Vec<ModRemoveCommentView>,
pub removed_communities: Vec<ModRemoveCommunityView>, pub removed_communities: Vec<ModRemoveCommunityView>,
pub banned_from_community: Vec<ModBanFromCommunityView>, pub banned_from_community: Vec<ModBanFromCommunityView>,

View file

@ -38,7 +38,7 @@ pub enum UserOperation {
ListCommentReports, ListCommentReports,
CreatePostLike, CreatePostLike,
LockPost, LockPost,
StickyPost, FeaturePost,
MarkPostAsRead, MarkPostAsRead,
SavePost, SavePost,
CreatePostReport, CreatePostReport,

View file

@ -25,7 +25,7 @@ use activitypub_federation::{
use activitystreams_kinds::public; use activitystreams_kinds::public;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{CreatePost, EditPost, LockPost, PostResponse, StickyPost}, post::{CreatePost, EditPost, FeaturePost, LockPost, PostResponse},
utils::get_local_user_view_from_jwt, utils::get_local_user_view_from_jwt,
websocket::{send::send_post_ws_message, UserOperationCrud}, websocket::{send::send_post_ws_message, UserOperationCrud},
}; };
@ -101,7 +101,7 @@ impl SendActivity for LockPost {
} }
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl SendActivity for StickyPost { impl SendActivity for FeaturePost {
type Response = PostResponse; type Response = PostResponse;
async fn send_activity( 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 // 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, // 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. // 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); 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( return Err(LemmyError::from_message(
"New post cannot be stickied or locked", "New post cannot be stickied or locked",
)); ));

View file

@ -32,7 +32,7 @@ use lemmy_db_schema::{
source::{ source::{
community::Community, community::Community,
local_site::LocalSite, local_site::LocalSite,
moderator::{ModLockPost, ModLockPostForm, ModStickyPost, ModStickyPostForm}, moderator::{ModFeaturePost, ModFeaturePostForm, ModLockPost, ModLockPostForm},
person::Person, person::Person,
post::{Post, PostInsertForm, PostUpdateForm}, post::{Post, PostInsertForm, PostUpdateForm},
}, },
@ -116,7 +116,7 @@ impl ApubObject for ApubPost {
image: self.thumbnail_url.clone().map(ImageObject::new), image: self.thumbnail_url.clone().map(ImageObject::new),
comments_enabled: Some(!self.locked), comments_enabled: Some(!self.locked),
sensitive: Some(self.nsfw), sensitive: Some(self.nsfw),
stickied: Some(self.stickied), stickied: Some(self.featured_community),
language, language,
published: Some(convert_datetime(self.published)), published: Some(convert_datetime(self.published)),
updated: self.updated.map(convert_datetime), updated: self.updated.map(convert_datetime),
@ -208,7 +208,6 @@ impl ApubObject for ApubPost {
updated: page.updated.map(|u| u.naive_local()), updated: page.updated.map(|u| u.naive_local()),
deleted: Some(false), deleted: Some(false),
nsfw: page.sensitive, nsfw: page.sensitive,
stickied: page.stickied,
embed_title, embed_title,
embed_description, embed_description,
embed_video_url, embed_video_url,
@ -216,6 +215,8 @@ impl ApubObject for ApubPost {
ap_id: Some(page.id.clone().into()), ap_id: Some(page.id.clone().into()),
local: Some(false), local: Some(false),
language_id, language_id,
featured_community: page.stickied,
featured_local: None,
} }
} else { } else {
// if is mod action, only update locked/stickied fields, nothing else // if is mod action, only update locked/stickied fields, nothing else
@ -225,7 +226,7 @@ impl ApubObject for ApubPost {
.community_id(community.id) .community_id(community.id)
.ap_id(Some(page.id.clone().into())) .ap_id(Some(page.id.clone().into()))
.locked(page.comments_enabled.map(|e| !e)) .locked(page.comments_enabled.map(|e| !e))
.stickied(page.stickied) .featured_community(page.stickied)
.updated(page.updated.map(|u| u.naive_local())) .updated(page.updated.map(|u| u.naive_local()))
.build() .build()
}; };
@ -236,14 +237,15 @@ impl ApubObject for ApubPost {
let post = Post::create(context.pool(), &form).await?; let post = Post::create(context.pool(), &form).await?;
// write mod log entries for sticky/lock // write mod log entries for feature/lock
if Page::is_stickied_changed(&old_post, &page.stickied) { if Page::is_featured_changed(&old_post, &page.stickied) {
let form = ModStickyPostForm { let form = ModFeaturePostForm {
mod_person_id: creator.id, mod_person_id: creator.id,
post_id: post.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) { if Page::is_locked_changed(&old_post, &page.comments_enabled) {
let form = ModLockPostForm { let form = ModLockPostForm {
@ -295,7 +297,7 @@ mod tests {
assert!(post.body.is_some()); assert!(post.body.is_some());
assert_eq!(post.body.as_ref().unwrap().len(), 45); assert_eq!(post.body.as_ref().unwrap().len(), 45);
assert!(!post.locked); assert!(!post.locked);
assert!(post.stickied); assert!(post.featured_community);
assert_eq!(request_counter, 0); assert_eq!(request_counter, 0);
Post::delete(context.pool(), post.id).await.unwrap(); Post::delete(context.pool(), post.id).await.unwrap();

View file

@ -137,18 +137,18 @@ impl Page {
.dereference_local(context) .dereference_local(context)
.await; .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); 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<E>( pub(crate) fn is_featured_changed<E>(
old_post: &Result<ApubPost, E>, old_post: &Result<ApubPost, E>,
new_stickied: &Option<bool>, new_featured_community: &Option<bool>,
) -> bool { ) -> bool {
if let Some(new_stickied) = new_stickied { if let Some(new_featured_community) = new_featured_community {
if let Ok(old_post) = old_post { if let Ok(old_post) = old_post {
return new_stickied != &old_post.stickied; return new_featured_community != &old_post.featured_community;
} }
} }

View file

@ -68,10 +68,11 @@ pub struct PostAggregates {
pub score: i64, pub score: i64,
pub upvotes: i64, pub upvotes: i64,
pub downvotes: i64, pub downvotes: i64,
pub stickied: bool,
pub published: chrono::NaiveDateTime, 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_necro: chrono::NaiveDateTime, // A newest comment time, limited to 2 days, to prevent necrobumping
pub newest_comment_time: chrono::NaiveDateTime, pub newest_comment_time: chrono::NaiveDateTime,
pub featured_community: bool,
pub featured_local: bool,
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]

View file

@ -16,6 +16,8 @@ use crate::{
ModBanForm, ModBanForm,
ModBanFromCommunity, ModBanFromCommunity,
ModBanFromCommunityForm, ModBanFromCommunityForm,
ModFeaturePost,
ModFeaturePostForm,
ModHideCommunity, ModHideCommunity,
ModHideCommunityForm, ModHideCommunityForm,
ModLockPost, ModLockPost,
@ -26,8 +28,6 @@ use crate::{
ModRemoveCommunityForm, ModRemoveCommunityForm,
ModRemovePost, ModRemovePost,
ModRemovePostForm, ModRemovePostForm,
ModStickyPost,
ModStickyPostForm,
ModTransferCommunity, ModTransferCommunity,
ModTransferCommunityForm, ModTransferCommunityForm,
}, },
@ -98,29 +98,29 @@ impl Crud for ModLockPost {
} }
#[async_trait] #[async_trait]
impl Crud for ModStickyPost { impl Crud for ModFeaturePost {
type InsertForm = ModStickyPostForm; type InsertForm = ModFeaturePostForm;
type UpdateForm = ModStickyPostForm; type UpdateForm = ModFeaturePostForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &DbPool, from_id: i32) -> Result<Self, Error> { async fn read(pool: &DbPool, from_id: i32) -> Result<Self, Error> {
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?; let conn = &mut get_conn(pool).await?;
mod_sticky_post.find(from_id).first::<Self>(conn).await mod_feature_post.find(from_id).first::<Self>(conn).await
} }
async fn create(pool: &DbPool, form: &ModStickyPostForm) -> Result<Self, Error> { async fn create(pool: &DbPool, form: &ModFeaturePostForm) -> Result<Self, Error> {
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?; let conn = &mut get_conn(pool).await?;
insert_into(mod_sticky_post) insert_into(mod_feature_post)
.values(form) .values(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn update(pool: &DbPool, from_id: i32, form: &ModStickyPostForm) -> Result<Self, Error> { async fn update(pool: &DbPool, from_id: i32, form: &ModFeaturePostForm) -> Result<Self, Error> {
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?; 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) .set(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
@ -525,6 +525,8 @@ mod tests {
ModBanForm, ModBanForm,
ModBanFromCommunity, ModBanFromCommunity,
ModBanFromCommunityForm, ModBanFromCommunityForm,
ModFeaturePost,
ModFeaturePostForm,
ModLockPost, ModLockPost,
ModLockPostForm, ModLockPostForm,
ModRemoveComment, ModRemoveComment,
@ -533,8 +535,6 @@ mod tests {
ModRemoveCommunityForm, ModRemoveCommunityForm,
ModRemovePost, ModRemovePost,
ModRemovePostForm, ModRemovePostForm,
ModStickyPost,
ModStickyPostForm,
}, },
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
post::{Post, PostInsertForm}, post::{Post, PostInsertForm},
@ -637,25 +637,27 @@ mod tests {
when_: inserted_mod_lock_post.when_, 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, mod_person_id: inserted_mod.id,
post_id: inserted_post.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 .await
.unwrap(); .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 .await
.unwrap(); .unwrap();
let expected_mod_sticky_post = ModStickyPost { let expected_mod_feature_post = ModFeaturePost {
id: inserted_mod_sticky_post.id, id: inserted_mod_feature_post.id,
post_id: inserted_post.id, post_id: inserted_post.id,
mod_person_id: inserted_mod.id, mod_person_id: inserted_mod.id,
stickied: Some(true), featured: false,
when_: inserted_mod_sticky_post.when_, is_featured_community: true,
when_: inserted_mod_feature_post.when_,
}; };
// comment // comment
@ -809,7 +811,7 @@ mod tests {
assert_eq!(expected_mod_remove_post, read_mod_remove_post); assert_eq!(expected_mod_remove_post, read_mod_remove_post);
assert_eq!(expected_mod_lock_post, read_mod_lock_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_comment, read_mod_remove_comment);
assert_eq!(expected_mod_remove_community, read_mod_remove_community); assert_eq!(expected_mod_remove_community, read_mod_remove_community);
assert_eq!(expected_mod_ban_from_community, read_mod_ban_from_community); assert_eq!(expected_mod_ban_from_community, read_mod_ban_from_community);

View file

@ -6,11 +6,11 @@ use crate::{
community_id, community_id,
creator_id, creator_id,
deleted, deleted,
featured_community,
name, name,
post, post,
published, published,
removed, removed,
stickied,
thumbnail_url, thumbnail_url,
updated, updated,
url, url,
@ -83,7 +83,7 @@ impl Post {
.filter(deleted.eq(false)) .filter(deleted.eq(false))
.filter(removed.eq(false)) .filter(removed.eq(false))
.then_order_by(published.desc()) .then_order_by(published.desc())
.then_order_by(stickied.desc()) .then_order_by(featured_community.desc())
.limit(FETCH_LIMIT_MAX) .limit(FETCH_LIMIT_MAX)
.load::<Self>(conn) .load::<Self>(conn)
.await .await
@ -381,7 +381,6 @@ mod tests {
published: inserted_post.published, published: inserted_post.published,
removed: false, removed: false,
locked: false, locked: false,
stickied: false,
nsfw: false, nsfw: false,
deleted: false, deleted: false,
updated: None, updated: None,
@ -392,6 +391,8 @@ mod tests {
ap_id: inserted_post.ap_id.clone(), ap_id: inserted_post.ap_id.clone(),
local: true, local: true,
language_id: Default::default(), language_id: Default::default(),
featured_community: false,
featured_local: false,
}; };
// Post Like // Post Like

View file

@ -82,7 +82,7 @@ pub enum ModlogActionType {
All, All,
ModRemovePost, ModRemovePost,
ModLockPost, ModLockPost,
ModStickyPost, ModFeaturePost,
ModRemoveComment, ModRemoveComment,
ModRemoveCommunity, ModRemoveCommunity,
ModBanFromCommunity, ModBanFromCommunity,
@ -96,3 +96,12 @@ pub enum ModlogActionType {
AdminPurgePost, AdminPurgePost,
AdminPurgeComment, AdminPurgeComment,
} }
#[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq,
)]
pub enum PostFeatureType {
#[default]
Local,
Community,
}

View file

@ -273,12 +273,13 @@ table! {
} }
table! { table! {
mod_sticky_post (id) { mod_feature_post (id) {
id -> Int4, id -> Int4,
mod_person_id -> Int4, mod_person_id -> Int4,
post_id -> Int4, post_id -> Int4,
stickied -> Nullable<Bool>, featured -> Bool,
when_ -> Timestamp, when_ -> Timestamp,
is_featured_community -> Bool,
} }
} }
@ -371,7 +372,6 @@ table! {
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
deleted -> Bool, deleted -> Bool,
nsfw -> Bool, nsfw -> Bool,
stickied -> Bool,
embed_title -> Nullable<Text>, embed_title -> Nullable<Text>,
embed_description -> Nullable<Text>, embed_description -> Nullable<Text>,
embed_video_url -> Nullable<Text>, embed_video_url -> Nullable<Text>,
@ -379,6 +379,8 @@ table! {
ap_id -> Varchar, ap_id -> Varchar,
local -> Bool, local -> Bool,
language_id -> Int4, language_id -> Int4,
featured_community -> Bool,
featured_local -> Bool,
} }
} }
@ -400,10 +402,11 @@ table! {
score -> Int8, score -> Int8,
upvotes -> Int8, upvotes -> Int8,
downvotes -> Int8, downvotes -> Int8,
stickied -> Bool,
published -> Timestamp, published -> Timestamp,
newest_comment_time_necro -> Timestamp, newest_comment_time_necro -> Timestamp,
newest_comment_time -> 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_community -> person (mod_person_id));
joinable!(mod_remove_post -> person (mod_person_id)); joinable!(mod_remove_post -> person (mod_person_id));
joinable!(mod_remove_post -> post (post_id)); joinable!(mod_remove_post -> post (post_id));
joinable!(mod_sticky_post -> person (mod_person_id)); joinable!(mod_feature_post -> person (mod_person_id));
joinable!(mod_sticky_post -> post (post_id)); joinable!(mod_feature_post -> post (post_id));
joinable!(password_reset_request -> local_user (local_user_id)); joinable!(password_reset_request -> local_user (local_user_id));
joinable!(person_aggregates -> person (person_id)); joinable!(person_aggregates -> person (person_id));
joinable!(person_ban -> 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_comment,
mod_remove_community, mod_remove_community,
mod_remove_post, mod_remove_post,
mod_sticky_post, mod_feature_post,
mod_hide_community, mod_hide_community,
password_reset_request, password_reset_request,
person, person,

View file

@ -9,12 +9,12 @@ use crate::schema::{
mod_add_community, mod_add_community,
mod_ban, mod_ban,
mod_ban_from_community, mod_ban_from_community,
mod_feature_post,
mod_hide_community, mod_hide_community,
mod_lock_post, mod_lock_post,
mod_remove_comment, mod_remove_comment,
mod_remove_community, mod_remove_community,
mod_remove_post, mod_remove_post,
mod_sticky_post,
mod_transfer_community, mod_transfer_community,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -61,21 +61,23 @@ pub struct ModLockPostForm {
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))] #[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
#[cfg_attr(feature = "full", diesel(table_name = mod_sticky_post))] #[cfg_attr(feature = "full", diesel(table_name = mod_feature_post))]
pub struct ModStickyPost { pub struct ModFeaturePost {
pub id: i32, pub id: i32,
pub mod_person_id: PersonId, pub mod_person_id: PersonId,
pub post_id: PostId, pub post_id: PostId,
pub stickied: Option<bool>, pub featured: bool,
pub when_: chrono::NaiveDateTime, pub when_: chrono::NaiveDateTime,
pub is_featured_community: bool,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = mod_sticky_post))] #[cfg_attr(feature = "full", diesel(table_name = mod_feature_post))]
pub struct ModStickyPostForm { pub struct ModFeaturePostForm {
pub mod_person_id: PersonId, pub mod_person_id: PersonId,
pub post_id: PostId, pub post_id: PostId,
pub stickied: Option<bool>, pub featured: bool,
pub is_featured_community: bool,
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]

View file

@ -20,7 +20,6 @@ pub struct Post {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool, pub nsfw: bool,
pub stickied: bool,
pub embed_title: Option<String>, pub embed_title: Option<String>,
pub embed_description: Option<String>, pub embed_description: Option<String>,
pub embed_video_url: Option<DbUrl>, pub embed_video_url: Option<DbUrl>,
@ -28,6 +27,8 @@ pub struct Post {
pub ap_id: DbUrl, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
pub language_id: LanguageId, pub language_id: LanguageId,
pub featured_community: bool,
pub featured_local: bool,
} }
#[derive(Debug, Clone, TypedBuilder)] #[derive(Debug, Clone, TypedBuilder)]
@ -49,7 +50,6 @@ pub struct PostInsertForm {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub published: Option<chrono::NaiveDateTime>, pub published: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub stickied: Option<bool>,
pub embed_title: Option<String>, pub embed_title: Option<String>,
pub embed_description: Option<String>, pub embed_description: Option<String>,
pub embed_video_url: Option<DbUrl>, pub embed_video_url: Option<DbUrl>,
@ -57,6 +57,8 @@ pub struct PostInsertForm {
pub ap_id: Option<DbUrl>, pub ap_id: Option<DbUrl>,
pub local: Option<bool>, pub local: Option<bool>,
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
pub featured_community: Option<bool>,
pub featured_local: Option<bool>,
} }
#[derive(Debug, Clone, TypedBuilder)] #[derive(Debug, Clone, TypedBuilder)]
@ -73,7 +75,6 @@ pub struct PostUpdateForm {
pub published: Option<chrono::NaiveDateTime>, pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<Option<chrono::NaiveDateTime>>, pub updated: Option<Option<chrono::NaiveDateTime>>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub stickied: Option<bool>,
pub embed_title: Option<Option<String>>, pub embed_title: Option<Option<String>>,
pub embed_description: Option<Option<String>>, pub embed_description: Option<Option<String>>,
pub embed_video_url: Option<Option<DbUrl>>, pub embed_video_url: Option<Option<DbUrl>>,
@ -81,6 +82,8 @@ pub struct PostUpdateForm {
pub ap_id: Option<DbUrl>, pub ap_id: Option<DbUrl>,
pub local: Option<bool>, pub local: Option<bool>,
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
pub featured_community: Option<bool>,
pub featured_local: Option<bool>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

View file

@ -863,7 +863,6 @@ mod tests {
removed: false, removed: false,
deleted: false, deleted: false,
locked: false, locked: false,
stickied: false,
nsfw: false, nsfw: false,
embed_title: None, embed_title: None,
embed_description: None, embed_description: None,
@ -872,6 +871,8 @@ mod tests {
ap_id: data.inserted_post.ap_id.clone(), ap_id: data.inserted_post.ap_id.clone(),
local: true, local: true,
language_id: Default::default(), language_id: Default::default(),
featured_community: false,
featured_local: false,
}, },
community: CommunitySafe { community: CommunitySafe {
id: data.inserted_community.id, id: data.inserted_community.id,

View file

@ -469,10 +469,11 @@ mod tests {
score: 0, score: 0,
upvotes: 0, upvotes: 0,
downvotes: 0, downvotes: 0,
stickied: false,
published: agg.published, published: agg.published,
newest_comment_time_necro: inserted_post.published, newest_comment_time_necro: inserted_post.published,
newest_comment_time: inserted_post.published, newest_comment_time: inserted_post.published,
featured_community: false,
featured_local: false,
}, },
resolver: None, resolver: None,
}; };

View file

@ -324,17 +324,16 @@ impl<'a> PostQuery<'a> {
} }
} }
} }
if self.community_id.is_none() && self.community_actor_id.is_none() {
if let Some(community_id) = self.community_id { query = query.then_order_by(post_aggregates::featured_local.desc());
} else if let Some(community_id) = self.community_id {
query = query query = query
.filter(post::community_id.eq(community_id)) .filter(post::community_id.eq(community_id))
.then_order_by(post_aggregates::stickied.desc()); .then_order_by(post_aggregates::featured_community.desc());
} } else if let Some(community_actor_id) = self.community_actor_id {
if let Some(community_actor_id) = self.community_actor_id {
query = query query = query
.filter(community::actor_id.eq(community_actor_id)) .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 { if let Some(url_search) = self.url_search {
@ -860,7 +859,6 @@ mod tests {
removed: false, removed: false,
deleted: false, deleted: false,
locked: false, locked: false,
stickied: false,
nsfw: false, nsfw: false,
embed_title: None, embed_title: None,
embed_description: None, embed_description: None,
@ -869,6 +867,8 @@ mod tests {
ap_id: inserted_post.ap_id.clone(), ap_id: inserted_post.ap_id.clone(),
local: true, local: true,
language_id: LanguageId(47), language_id: LanguageId(47),
featured_community: false,
featured_local: false,
}, },
my_vote: None, my_vote: None,
unread_comments: 0, unread_comments: 0,
@ -919,10 +919,11 @@ mod tests {
score: 0, score: 0,
upvotes: 0, upvotes: 0,
downvotes: 0, downvotes: 0,
stickied: false,
published: agg.published, published: agg.published,
newest_comment_time_necro: inserted_post.published, newest_comment_time_necro: inserted_post.published,
newest_comment_time: inserted_post.published, newest_comment_time: inserted_post.published,
featured_community: false,
featured_local: false,
}, },
subscribed: SubscribedType::NotSubscribed, subscribed: SubscribedType::NotSubscribed,
read: false, read: false,

View file

@ -15,6 +15,8 @@ pub mod mod_ban_from_community_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod mod_ban_view; pub mod mod_ban_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod mod_feature_post_view;
#[cfg(feature = "full")]
pub mod mod_hide_community_view; pub mod mod_hide_community_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod mod_lock_post_view; pub mod mod_lock_post_view;
@ -25,7 +27,5 @@ pub mod mod_remove_community_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod mod_remove_post_view; pub mod mod_remove_post_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod mod_sticky_post_view;
#[cfg(feature = "full")]
pub mod mod_transfer_community_view; pub mod mod_transfer_community_view;
pub mod structs; pub mod structs;

View file

@ -1,4 +1,4 @@
use crate::structs::{ModStickyPostView, ModlogListParams}; use crate::structs::{ModFeaturePostView, ModlogListParams};
use diesel::{ use diesel::{
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
@ -11,10 +11,10 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PersonId, newtypes::PersonId,
schema::{community, mod_sticky_post, person, post}, schema::{community, mod_feature_post, person, post},
source::{ source::{
community::{Community, CommunitySafe}, community::{Community, CommunitySafe},
moderator::ModStickyPost, moderator::ModFeaturePost,
person::{Person, PersonSafe}, person::{Person, PersonSafe},
post::Post, post::Post,
}, },
@ -22,9 +22,9 @@ use lemmy_db_schema::{
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbPool},
}; };
type ModStickyPostViewTuple = (ModStickyPost, Option<PersonSafe>, Post, CommunitySafe); type ModFeaturePostViewTuple = (ModFeaturePost, Option<PersonSafe>, Post, CommunitySafe);
impl ModStickyPostView { impl ModFeaturePostView {
pub async fn list(pool: &DbPool, params: ModlogListParams) -> Result<Vec<Self>, Error> { pub async fn list(pool: &DbPool, params: ModlogListParams) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let person_alias_1 = diesel::alias!(person as person1); 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 = !params.hide_modlog_names;
let show_mod_names_expr = show_mod_names.as_sql::<diesel::sql_types::Bool>(); let show_mod_names_expr = show_mod_names.as_sql::<diesel::sql_types::Bool>();
let admin_names_join = mod_sticky_post::mod_person_id let admin_names_join = mod_feature_post::mod_person_id
.eq(person::id) .eq(person::id)
.and(show_mod_names_expr.or(person::id.eq(admin_person_id_join))); .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)) .left_join(person::table.on(admin_names_join))
.inner_join(post::table) .inner_join(post::table)
.inner_join(person_alias_1.on(post::creator_id.eq(person_alias_1.field(person::id)))) .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))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.select(( .select((
mod_sticky_post::all_columns, mod_feature_post::all_columns,
Person::safe_columns_tuple().nullable(), Person::safe_columns_tuple().nullable(),
post::all_columns, post::all_columns,
Community::safe_columns_tuple(), Community::safe_columns_tuple(),
@ -53,7 +53,7 @@ impl ModStickyPostView {
}; };
if let Some(mod_person_id) = params.mod_person_id { 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 { if let Some(other_person_id) = params.other_person_id {
@ -65,8 +65,8 @@ impl ModStickyPostView {
let res = query let res = query
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.order_by(mod_sticky_post::when_.desc()) .order_by(mod_feature_post::when_.desc())
.load::<ModStickyPostViewTuple>(conn) .load::<ModFeaturePostViewTuple>(conn)
.await?; .await?;
let results = Self::from_tuple_to_vec(res); let results = Self::from_tuple_to_vec(res);
@ -74,13 +74,13 @@ impl ModStickyPostView {
} }
} }
impl ViewToVec for ModStickyPostView { impl ViewToVec for ModFeaturePostView {
type DbTuple = ModStickyPostViewTuple; type DbTuple = ModFeaturePostViewTuple;
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> { fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
items items
.into_iter() .into_iter()
.map(|a| Self { .map(|a| Self {
mod_sticky_post: a.0, mod_feature_post: a.0,
moderator: a.1, moderator: a.1,
post: a.2, post: a.2,
community: a.3, community: a.3,

View file

@ -12,12 +12,12 @@ use lemmy_db_schema::{
ModAddCommunity, ModAddCommunity,
ModBan, ModBan,
ModBanFromCommunity, ModBanFromCommunity,
ModFeaturePost,
ModHideCommunity, ModHideCommunity,
ModLockPost, ModLockPost,
ModRemoveComment, ModRemoveComment,
ModRemoveCommunity, ModRemoveCommunity,
ModRemovePost, ModRemovePost,
ModStickyPost,
ModTransferCommunity, ModTransferCommunity,
}, },
person::PersonSafe, person::PersonSafe,
@ -97,8 +97,8 @@ pub struct ModRemovePostView {
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ModStickyPostView { pub struct ModFeaturePostView {
pub mod_sticky_post: ModStickyPost, pub mod_feature_post: ModFeaturePost,
pub moderator: Option<PersonSafe>, pub moderator: Option<PersonSafe>,
pub post: Post, pub post: Post,
pub community: CommunitySafe, pub community: CommunitySafe,

View file

@ -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();

View file

@ -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();

View file

@ -59,6 +59,7 @@ use lemmy_api_common::{
CreatePostReport, CreatePostReport,
DeletePost, DeletePost,
EditPost, EditPost,
FeaturePost,
GetPost, GetPost,
GetPosts, GetPosts,
GetSiteMetadata, GetSiteMetadata,
@ -68,7 +69,6 @@ use lemmy_api_common::{
RemovePost, RemovePost,
ResolvePostReport, ResolvePostReport,
SavePost, SavePost,
StickyPost,
}, },
private_message::{ private_message::{
CreatePrivateMessage, CreatePrivateMessage,
@ -183,7 +183,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
web::post().to(route_post::<MarkPostAsRead>), web::post().to(route_post::<MarkPostAsRead>),
) )
.route("/lock", web::post().to(route_post::<LockPost>)) .route("/lock", web::post().to(route_post::<LockPost>))
.route("/sticky", web::post().to(route_post::<StickyPost>)) .route("/feature", web::post().to(route_post::<FeaturePost>))
.route("/list", web::get().to(route_get_apub::<GetPosts>)) .route("/list", web::get().to(route_get_apub::<GetPosts>))
.route("/like", web::post().to(route_post::<CreatePostLike>)) .route("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>)) .route("/save", web::put().to(route_post::<SavePost>))

View file

@ -60,6 +60,7 @@ use lemmy_api_common::{
CreatePostReport, CreatePostReport,
DeletePost, DeletePost,
EditPost, EditPost,
FeaturePost,
GetPost, GetPost,
GetPosts, GetPosts,
GetSiteMetadata, GetSiteMetadata,
@ -69,7 +70,6 @@ use lemmy_api_common::{
RemovePost, RemovePost,
ResolvePostReport, ResolvePostReport,
SavePost, SavePost,
StickyPost,
}, },
private_message::{ private_message::{
CreatePrivateMessage, CreatePrivateMessage,
@ -560,7 +560,9 @@ pub async fn match_websocket_operation(
// Post ops // Post ops
UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await, UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await, UserOperation::FeaturePost => {
do_websocket_operation::<FeaturePost>(context, id, op, data).await
}
UserOperation::CreatePostLike => { UserOperation::CreatePostLike => {
do_websocket_operation::<CreatePostLike>(context, id, op, data).await do_websocket_operation::<CreatePostLike>(context, id, op, data).await
} }