Skip to content

Commit

Permalink
Add support for Featured Posts (#2585)
Browse files Browse the repository at this point in the history
* Add support for Featured Posts

* Fix rebase

* More fixes
  • Loading branch information
makotech222 committed Dec 12, 2022
1 parent 0ecf256 commit 9dfd819
Show file tree
Hide file tree
Showing 30 changed files with 319 additions and 156 deletions.
2 changes: 1 addition & 1 deletion api_tests/package.json
Expand Up @@ -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",
Expand Down
20 changes: 10 additions & 10 deletions api_tests/src/post.spec.ts
Expand Up @@ -11,7 +11,7 @@ import {
setupLogins,
createPost,
editPost,
stickyPost,
featurePost,
lockPost,
resolvePost,
likePost,
Expand Down Expand Up @@ -157,39 +157,39 @@ 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 = (
await resolvePost(beta, postRes.post_view.post)
).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 = (
await resolvePost(beta, postRes.post_view.post)
).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 () => {
Expand Down
17 changes: 9 additions & 8 deletions api_tests/src/shared.ts
Expand Up @@ -7,7 +7,6 @@ import {
CreateComment,
DeletePost,
RemovePost,
StickyPost,
LockPost,
PostResponse,
SearchResponse,
Expand Down Expand Up @@ -64,6 +63,8 @@ import {
CommentSortType,
GetComments,
GetCommentsResponse,
FeaturePost,
PostFeatureType,
} from "lemmy-js-client";

export interface API {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<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(
Expand Down
20 changes: 16 additions & 4 deletions api_tests/yarn.lock
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
54 changes: 32 additions & 22 deletions crates/api/src/post/sticky.rs → crates/api/src/post/feature.rs
Expand Up @@ -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))]
Expand All @@ -30,7 +32,7 @@ impl Perform for StickyPost {
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?;

Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion 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;
8 changes: 4 additions & 4 deletions crates/api/src/site/mod_log.rs
Expand Up @@ -19,12 +19,12 @@ use lemmy_db_views_moderator::structs::{
ModAddView,
ModBanFromCommunityView,
ModBanView,
ModFeaturePostView,
ModHideCommunityView,
ModLockPostView,
ModRemoveCommentView,
ModRemoveCommunityView,
ModRemovePostView,
ModStickyPostView,
ModTransferCommunityView,
ModlogListParams,
};
Expand Down Expand Up @@ -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(),
};

Expand Down Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions crates/api_common/src/post.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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<String>,
}

Expand Down
4 changes: 2 additions & 2 deletions crates/api_common/src/site.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -93,7 +93,7 @@ pub struct GetModlog {
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>,
Expand Down
2 changes: 1 addition & 1 deletion crates/api_common/src/websocket/mod.rs
Expand Up @@ -38,7 +38,7 @@ pub enum UserOperation {
ListCommentReports,
CreatePostLike,
LockPost,
StickyPost,
FeaturePost,
MarkPostAsRead,
SavePost,
CreatePostReport,
Expand Down
8 changes: 4 additions & 4 deletions crates/apub/src/activities/create_or_update/post.rs
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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",
));
Expand Down

0 comments on commit 9dfd819

Please sign in to comment.