"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",
setupLogins,
createPost,
editPost,
- stickyPost,
+ featurePost,
lockPost,
resolvePost,
likePost,
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 = (
).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 = (
).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 () => {
CreateComment,
DeletePost,
RemovePost,
- StickyPost,
LockPost,
PostResponse,
SearchResponse,
CommentSortType,
GetComments,
GetCommentsResponse,
+ FeaturePost,
+ PostFeatureType,
} from "lemmy-js-client";
export interface API {
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
return api.client.removePost(form);
}
-export async function stickyPost(
+export async function featurePost(
api: API,
- stickied: boolean,
+ featured: boolean,
post: Post
): Promise<PostResponse> {
- 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(
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"
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"
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))]
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<PostResponse, LemmyError> {
- 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?;
.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,
+mod feature;
mod get_link_metadata;
mod like;
mod lock;
mod mark_read;
mod save;
-mod sticky;
ModAddView,
ModBanFromCommunityView,
ModBanView,
+ ModFeaturePostView,
ModHideCommunityView,
ModLockPostView,
ModRemoveCommentView,
ModRemoveCommunityView,
ModRemovePostView,
- ModStickyPostView,
ModTransferCommunityView,
ModlogListParams,
};
_ => 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(),
};
Ok(GetModlogResponse {
removed_posts,
locked_posts,
- stickied_posts,
+ featured_posts,
removed_comments,
removed_communities,
banned_from_community,
use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
ListingType,
+ PostFeatureType,
SortType,
};
use lemmy_db_views::structs::{PostReportView, PostView};
}
#[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<String>,
}
ModAddView,
ModBanFromCommunityView,
ModBanView,
+ ModFeaturePostView,
ModHideCommunityView,
ModLockPostView,
ModRemoveCommentView,
ModRemoveCommunityView,
ModRemovePostView,
- ModStickyPostView,
ModTransferCommunityView,
};
use serde::{Deserialize, Serialize};
pub struct GetModlogResponse {
pub removed_posts: Vec<ModRemovePostView>,
pub locked_posts: Vec<ModLockPostView>,
- pub stickied_posts: Vec<ModStickyPostView>,
+ pub featured_posts: Vec<ModFeaturePostView>,
pub removed_comments: Vec<ModRemoveCommentView>,
pub removed_communities: Vec<ModRemoveCommunityView>,
pub banned_from_community: Vec<ModBanFromCommunityView>,
ListCommentReports,
CreatePostLike,
LockPost,
- StickyPost,
+ FeaturePost,
MarkPostAsRead,
SavePost,
CreatePostReport,
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},
};
}
#[async_trait::async_trait(?Send)]
-impl SendActivity for StickyPost {
+impl SendActivity for FeaturePost {
type Response = PostResponse;
async fn send_activity(
// 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",
));
source::{
community::Community,
local_site::LocalSite,
- moderator::{ModLockPost, ModLockPostForm, ModStickyPost, ModStickyPostForm},
+ moderator::{ModFeaturePost, ModFeaturePostForm, ModLockPost, ModLockPostForm},
person::Person,
post::{Post, PostInsertForm, PostUpdateForm},
},
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),
updated: page.updated.map(|u| u.naive_local()),
deleted: Some(false),
nsfw: page.sensitive,
- stickied: page.stickied,
embed_title,
embed_description,
embed_video_url,
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
.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()
};
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 {
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();
.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<E>(
+ pub(crate) fn is_featured_changed<E>(
old_post: &Result<ApubPost, E>,
- new_stickied: &Option<bool>,
+ new_featured_community: &Option<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 {
- return new_stickied != &old_post.stickied;
+ return new_featured_community != &old_post.featured_community;
}
}
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)]
ModBanForm,
ModBanFromCommunity,
ModBanFromCommunityForm,
+ ModFeaturePost,
+ ModFeaturePostForm,
ModHideCommunity,
ModHideCommunityForm,
ModLockPost,
ModRemoveCommunityForm,
ModRemovePost,
ModRemovePostForm,
- ModStickyPost,
- ModStickyPostForm,
ModTransferCommunity,
ModTransferCommunityForm,
},
}
#[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<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?;
- 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> {
- use crate::schema::mod_sticky_post::dsl::mod_sticky_post;
+ async fn create(pool: &DbPool, form: &ModFeaturePostForm) -> Result<Self, Error> {
+ 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::<Self>(conn)
.await
}
- async fn update(pool: &DbPool, from_id: i32, form: &ModStickyPostForm) -> Result<Self, Error> {
- use crate::schema::mod_sticky_post::dsl::mod_sticky_post;
+ async fn update(pool: &DbPool, from_id: i32, form: &ModFeaturePostForm) -> Result<Self, Error> {
+ 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::<Self>(conn)
.await
ModBanForm,
ModBanFromCommunity,
ModBanFromCommunityForm,
+ ModFeaturePost,
+ ModFeaturePostForm,
ModLockPost,
ModLockPostForm,
ModRemoveComment,
ModRemoveCommunityForm,
ModRemovePost,
ModRemovePostForm,
- ModStickyPost,
- ModStickyPostForm,
},
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
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
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);
community_id,
creator_id,
deleted,
+ featured_community,
name,
post,
published,
removed,
- stickied,
thumbnail_url,
updated,
url,
.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::<Self>(conn)
.await
published: inserted_post.published,
removed: false,
locked: false,
- stickied: false,
nsfw: false,
deleted: false,
updated: None,
ap_id: inserted_post.ap_id.clone(),
local: true,
language_id: Default::default(),
+ featured_community: false,
+ featured_local: false,
};
// Post Like
All,
ModRemovePost,
ModLockPost,
- ModStickyPost,
+ ModFeaturePost,
ModRemoveComment,
ModRemoveCommunity,
ModBanFromCommunity,
AdminPurgePost,
AdminPurgeComment,
}
+
+#[derive(
+ EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq,
+)]
+pub enum PostFeatureType {
+ #[default]
+ Local,
+ Community,
+}
}
table! {
- mod_sticky_post (id) {
+ mod_feature_post (id) {
id -> Int4,
mod_person_id -> Int4,
post_id -> Int4,
- stickied -> Nullable<Bool>,
+ featured -> Bool,
when_ -> Timestamp,
+ is_featured_community -> Bool,
}
}
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
- stickied -> Bool,
embed_title -> Nullable<Text>,
embed_description -> Nullable<Text>,
embed_video_url -> Nullable<Text>,
ap_id -> Varchar,
local -> Bool,
language_id -> Int4,
+ featured_community -> Bool,
+ featured_local -> Bool,
}
}
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,
}
}
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));
mod_remove_comment,
mod_remove_community,
mod_remove_post,
- mod_sticky_post,
+ mod_feature_post,
mod_hide_community,
password_reset_request,
person,
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};
#[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<bool>,
+ 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<bool>,
+ pub featured: bool,
+ pub is_featured_community: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub nsfw: bool,
- pub stickied: bool,
pub embed_title: Option<String>,
pub embed_description: Option<String>,
pub embed_video_url: Option<DbUrl>,
pub ap_id: DbUrl,
pub local: bool,
pub language_id: LanguageId,
+ pub featured_community: bool,
+ pub featured_local: bool,
}
#[derive(Debug, Clone, TypedBuilder)]
pub updated: Option<chrono::NaiveDateTime>,
pub published: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
- pub stickied: Option<bool>,
pub embed_title: Option<String>,
pub embed_description: Option<String>,
pub embed_video_url: Option<DbUrl>,
pub ap_id: Option<DbUrl>,
pub local: Option<bool>,
pub language_id: Option<LanguageId>,
+ pub featured_community: Option<bool>,
+ pub featured_local: Option<bool>,
}
#[derive(Debug, Clone, TypedBuilder)]
pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<Option<chrono::NaiveDateTime>>,
pub deleted: Option<bool>,
- pub stickied: Option<bool>,
pub embed_title: Option<Option<String>>,
pub embed_description: Option<Option<String>>,
pub embed_video_url: Option<Option<DbUrl>>,
pub ap_id: Option<DbUrl>,
pub local: Option<bool>,
pub language_id: Option<LanguageId>,
+ pub featured_community: Option<bool>,
+ pub featured_local: Option<bool>,
}
#[derive(PartialEq, Eq, Debug)]
removed: false,
deleted: false,
locked: false,
- stickied: false,
nsfw: false,
embed_title: None,
embed_description: None,
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,
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,
};
}
}
}
-
- 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 {
removed: false,
deleted: false,
locked: false,
- stickied: false,
nsfw: false,
embed_title: None,
embed_description: None,
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,
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,
#[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;
#[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;
-use crate::structs::{ModStickyPostView, ModlogListParams};
+use crate::structs::{ModFeaturePostView, ModlogListParams};
use diesel::{
result::Error,
BoolExpressionMethods,
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,
},
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> {
let conn = &mut get_conn(pool).await?;
let person_alias_1 = diesel::alias!(person as person1);
let show_mod_names = !params.hide_modlog_names;
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)
.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(),
};
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 {
let res = query
.limit(limit)
.offset(offset)
- .order_by(mod_sticky_post::when_.desc())
- .load::<ModStickyPostViewTuple>(conn)
+ .order_by(mod_feature_post::when_.desc())
+ .load::<ModFeaturePostViewTuple>(conn)
.await?;
let results = Self::from_tuple_to_vec(res);
}
}
-impl ViewToVec for ModStickyPostView {
- type DbTuple = ModStickyPostViewTuple;
+impl ViewToVec for ModFeaturePostView {
+ type DbTuple = ModFeaturePostViewTuple;
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
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,
ModAddCommunity,
ModBan,
ModBanFromCommunity,
+ ModFeaturePost,
ModHideCommunity,
ModLockPost,
ModRemoveComment,
ModRemoveCommunity,
ModRemovePost,
- ModStickyPost,
ModTransferCommunity,
},
person::PersonSafe,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct ModStickyPostView {
- pub mod_sticky_post: ModStickyPost,
+pub struct ModFeaturePostView {
+ pub mod_feature_post: ModFeaturePost,
pub moderator: Option<PersonSafe>,
pub post: Post,
pub community: CommunitySafe,
--- /dev/null
+
+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
--- /dev/null
+
+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
CreatePostReport,
DeletePost,
EditPost,
+ FeaturePost,
GetPost,
GetPosts,
GetSiteMetadata,
RemovePost,
ResolvePostReport,
SavePost,
- StickyPost,
},
private_message::{
CreatePrivateMessage,
web::post().to(route_post::<MarkPostAsRead>),
)
.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("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>))
CreatePostReport,
DeletePost,
EditPost,
+ FeaturePost,
GetPost,
GetPosts,
GetSiteMetadata,
RemovePost,
ResolvePostReport,
SavePost,
- StickyPost,
},
private_message::{
CreatePrivateMessage,
// Post ops
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 => {
do_websocket_operation::<CreatePostLike>(context, id, op, data).await
}