Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Featured Posts #2585

Merged
merged 3 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api_tests/package.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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==

[email protected]:
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==
[email protected]:
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==

[email protected]:
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading