update moderator view (#3820)

* update api tests for new moderator view

* chage moderator view to be a listing type in get posts

Note: Internally, the listing type is called ListingType.ModeratorView,
but it's called "Moderator View" in the api endpoint

* fix formatting

* add support for moderator view to list comments

* add api test for moderator view when listing comments

* fix api test formatting

* retry tests

* don't filter out blocked users and communities when using moderator view

* fix cargo tests failing

* fix formatting

* fix previous merge

* Adding ModeratorView to listing_type_enums

* Fixing fmt.

* Adding a default to ListingType.

* Upgrading to use new lemmy-js-client.

---------

Co-authored-by: Nutomic <me@nutomic.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
This commit is contained in:
biosfood 2023-08-31 13:07:45 +02:00 committed by GitHub
parent c93bde9799
commit 384e55f0e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 140 additions and 30 deletions

View file

@ -19,7 +19,7 @@
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"lemmy-js-client": "0.19.0-rc.2", "lemmy-js-client": "0.19.0-rc.3",
"prettier": "^3.0.0", "prettier": "^3.0.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.0.4" "typescript": "^5.0.4"

View file

@ -21,6 +21,8 @@ import {
registerUser, registerUser,
API, API,
getPosts, getPosts,
getComments,
createComment,
getCommunityByName, getCommunityByName,
} from "./shared"; } from "./shared";
@ -245,12 +247,17 @@ test("moderator view", async () => {
client: alpha.client, client: alpha.client,
}; };
expect(otherUser.auth).not.toBe(""); expect(otherUser.auth).not.toBe("");
let otherCommunity = (await createCommunity(otherUser)).community_view; let otherCommunity = (await createCommunity(otherUser)).community_view;
expect(otherCommunity.community.name).toBeDefined(); expect(otherCommunity.community.name).toBeDefined();
let otherPost = (await createPost(otherUser, otherCommunity.community.id)) let otherPost = (await createPost(otherUser, otherCommunity.community.id))
.post_view; .post_view;
expect(otherPost.post.id).toBeDefined(); expect(otherPost.post.id).toBeDefined();
let otherComment = (await createComment(otherUser, otherPost.post.id))
.comment_view;
expect(otherComment.comment.id).toBeDefined();
// create a community and post on alpha // create a community and post on alpha
let alphaCommunity = (await createCommunity(alpha)).community_view; let alphaCommunity = (await createCommunity(alpha)).community_view;
expect(alphaCommunity.community.name).toBeDefined(); expect(alphaCommunity.community.name).toBeDefined();
@ -258,27 +265,56 @@ test("moderator view", async () => {
.post_view; .post_view;
expect(alphaPost.post.id).toBeDefined(); expect(alphaPost.post.id).toBeDefined();
let alphaComment = (await createComment(otherUser, alphaPost.post.id))
.comment_view;
expect(alphaComment.comment.id).toBeDefined();
// other user also posts on alpha's community // other user also posts on alpha's community
let otherAlphaPost = ( let otherAlphaPost = (
await createPost(otherUser, alphaCommunity.community.id) await createPost(otherUser, alphaCommunity.community.id)
).post_view; ).post_view;
expect(otherAlphaPost.post.id).toBeDefined(); expect(otherAlphaPost.post.id).toBeDefined();
// alpha lists posts on home page, should contain all posts that were made let otherAlphaComment = (
let posts = (await getPosts(alpha)).posts; await createComment(otherUser, otherAlphaPost.post.id)
).comment_view;
expect(otherAlphaComment.comment.id).toBeDefined();
// alpha lists posts and comments on home page, should contain all posts that were made
let posts = (await getPosts(alpha, "All")).posts;
expect(posts).toBeDefined(); expect(posts).toBeDefined();
let postIds = posts.map(post => post.post.id); let postIds = posts.map(post => post.post.id);
let comments = (await getComments(alpha, undefined, "All")).comments;
expect(comments).toBeDefined();
let commentIds = comments.map(comment => comment.comment.id);
expect(postIds).toContain(otherPost.post.id); expect(postIds).toContain(otherPost.post.id);
expect(commentIds).toContain(otherComment.comment.id);
expect(postIds).toContain(alphaPost.post.id); expect(postIds).toContain(alphaPost.post.id);
expect(commentIds).toContain(alphaComment.comment.id);
expect(postIds).toContain(otherAlphaPost.post.id); expect(postIds).toContain(otherAlphaPost.post.id);
expect(commentIds).toContain(otherAlphaComment.comment.id);
// in moderator view, alpha should not see otherPost, wich was posted on a community alpha doesn't moderate // in moderator view, alpha should not see otherPost, wich was posted on a community alpha doesn't moderate
posts = (await getPosts(alpha, true)).posts; posts = (await getPosts(alpha, "ModeratorView")).posts;
expect(posts).toBeDefined(); expect(posts).toBeDefined();
postIds = posts.map(post => post.post.id); postIds = posts.map(post => post.post.id);
comments = (await getComments(alpha, undefined, "ModeratorView")).comments;
expect(comments).toBeDefined();
commentIds = comments.map(comment => comment.comment.id);
expect(postIds).not.toContain(otherPost.post.id); expect(postIds).not.toContain(otherPost.post.id);
expect(commentIds).not.toContain(otherComment.comment.id);
expect(postIds).toContain(alphaPost.post.id); expect(postIds).toContain(alphaPost.post.id);
expect(commentIds).toContain(alphaComment.comment.id);
expect(postIds).toContain(otherAlphaPost.post.id); expect(postIds).toContain(otherAlphaPost.post.id);
expect(commentIds).toContain(otherAlphaComment.comment.id);
}); });
test("Get community for different casing on domain", async () => { test("Get community for different casing on domain", async () => {

View file

@ -4,7 +4,6 @@ import {
GetUnreadCount, GetUnreadCount,
GetUnreadCountResponse, GetUnreadCountResponse,
LemmyHttp, LemmyHttp,
LocalUser,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost"; import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
@ -69,6 +68,7 @@ import { GetPostsResponse } from "lemmy-js-client/dist/types/GetPostsResponse";
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts"; import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse"; import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse";
import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails"; import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
import { ListingType } from "lemmy-js-client/dist/types/ListingType";
export interface API { export interface API {
client: LemmyHttp; client: LemmyHttp;
@ -201,7 +201,9 @@ export async function setupLogins() {
try { try {
await createCommunity(alpha, "main"); await createCommunity(alpha, "main");
await createCommunity(beta, "main"); await createCommunity(beta, "main");
} catch (_) {} } catch (_) {
console.log("Communities already exist");
}
} }
export async function createPost( export async function createPost(
@ -321,11 +323,12 @@ export async function getPost(
export async function getComments( export async function getComments(
api: API, api: API,
post_id: number, post_id?: number,
listingType: ListingType = "All",
): Promise<GetCommentsResponse> { ): Promise<GetCommentsResponse> {
let form: GetComments = { let form: GetComments = {
post_id: post_id, post_id: post_id,
type_: "All", type_: listingType,
sort: "New", sort: "New",
auth: api.auth, auth: api.auth,
}; };
@ -798,11 +801,11 @@ export async function listCommentReports(
export function getPosts( export function getPosts(
api: API, api: API,
moderator_view = false, listingType?: ListingType,
): Promise<GetPostsResponse> { ): Promise<GetPostsResponse> {
let form: GetPosts = { let form: GetPosts = {
moderator_view,
auth: api.auth, auth: api.auth,
type_: listingType,
}; };
return api.client.getPosts(form); return api.client.getPosts(form);
} }

View file

@ -6,7 +6,7 @@
"noImplicitAny": true, "noImplicitAny": true,
"lib": ["es2017", "es7", "es6", "dom"], "lib": ["es2017", "es7", "es6", "dom"],
"outDir": "./dist", "outDir": "./dist",
"target": "ES5", "target": "ES2015",
"strictNullChecks": true, "strictNullChecks": true,
"moduleResolution": "Node" "moduleResolution": "Node"
}, },

View file

@ -2174,10 +2174,10 @@ 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.19.0-rc.2: lemmy-js-client@0.19.0-rc.3:
version "0.19.0-rc.2" version "0.19.0-rc.3"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.2.tgz#c3cb511b27f92538909a2b91a0f8527b1abad958" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.3.tgz#1efbfd5ce492319227a41cb020fc1cf9b2e7c075"
integrity sha512-FXuf8s7bpBVkHL/OGWDb/0aGIrJ7uv3d4Xt1h6zmNDhw6MmmuD8RXgCHiS2jqhxjAEp96Dpl1NFXbpmKpix7tQ== integrity sha512-RmibQ3+YTvqsQ89II2I29pfPmVAWiSObGAU9Nc/AGYfyvaCya7f5+TirKwHdKA2eWDWLOTnD4rm6WgcgAwvhWw==
dependencies: dependencies:
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
form-data "^4.0.0" form-data "^4.0.0"

View file

@ -77,7 +77,6 @@ pub struct GetPosts {
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
pub liked_only: Option<bool>, pub liked_only: Option<bool>,
pub disliked_only: Option<bool>, pub disliked_only: Option<bool>,
pub moderator_view: Option<bool>,
pub auth: Option<Sensitive<String>>, pub auth: Option<Sensitive<String>>,
} }

View file

@ -43,8 +43,6 @@ pub async fn list_posts(
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters)); return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
} }
let moderator_view = data.moderator_view.unwrap_or_default();
let listing_type = Some(listing_type_with_default( let listing_type = Some(listing_type_with_default(
data.type_, data.type_,
&local_site, &local_site,
@ -59,7 +57,6 @@ pub async fn list_posts(
saved_only, saved_only,
liked_only, liked_only,
disliked_only, disliked_only,
moderator_view,
page, page,
limit, limit,
..Default::default() ..Default::default()

View file

@ -44,7 +44,9 @@ use strum_macros::{Display, EnumString};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use ts_rs::TS; use ts_rs::TS;
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default,
)]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
feature = "full", feature = "full",
@ -54,6 +56,7 @@ use ts_rs::TS;
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The post sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html /// The post sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
pub enum SortType { pub enum SortType {
#[default]
Active, Active,
Hot, Hot,
New, New,
@ -99,7 +102,9 @@ pub enum PersonSortType {
PostCount, PostCount,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default,
)]
#[cfg_attr(feature = "full", derive(DbEnum, TS))] #[cfg_attr(feature = "full", derive(DbEnum, TS))]
#[cfg_attr( #[cfg_attr(
feature = "full", feature = "full",
@ -112,9 +117,12 @@ pub enum ListingType {
/// Content from your own site, as well as all connected / federated sites. /// Content from your own site, as well as all connected / federated sites.
All, All,
/// Content from your site only. /// Content from your site only.
#[default]
Local, Local,
/// Content only from communities you've subscribed to. /// Content only from communities you've subscribed to.
Subscribed, Subscribed,
/// Content that you can moderate (because you are a moderator of the community it is posted to)
ModeratorView,
} }
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]

View file

@ -22,6 +22,7 @@ use lemmy_db_schema::{
community, community,
community_block, community_block,
community_follower, community_follower,
community_moderator,
community_person_ban, community_person_ban,
local_user_language, local_user_language,
person, person,
@ -101,6 +102,14 @@ fn queries<'a>() -> Queries<
.and(comment_like::person_id.eq(person_id_join)), .and(comment_like::person_id.eq(person_id_join)),
), ),
) )
.left_join(
community_moderator::table.on(
post::id
.eq(comment::post_id)
.and(post::community_id.eq(community_moderator::community_id))
.and(community_moderator::person_id.eq(person_id_join)),
),
)
}; };
let selection = ( let selection = (
@ -186,6 +195,9 @@ fn queries<'a>() -> Queries<
.or(community_follower::person_id.eq(person_id_join)), .or(community_follower::person_id.eq(person_id_join)),
) )
} }
ListingType::ModeratorView => {
query = query.filter(community_moderator::person_id.is_not_null());
}
} }
} }
@ -222,7 +234,9 @@ fn queries<'a>() -> Queries<
query = query.filter(person::bot_account.eq(false)); query = query.filter(person::bot_account.eq(false));
}; };
if options.local_user.is_some() { if options.local_user.is_some()
&& options.listing_type.unwrap_or_default() != ListingType::ModeratorView
{
// Filter out the rows with missing languages // Filter out the rows with missing languages
query = query.filter(local_user_language::language_id.is_not_null()); query = query.filter(local_user_language::language_id.is_not_null());

View file

@ -258,6 +258,9 @@ fn queries<'a>() -> Queries<
.or(community_follower::person_id.eq(person_id_join)), .or(community_follower::person_id.eq(person_id_join)),
) )
} }
ListingType::ModeratorView => {
query = query.filter(community_moderator::person_id.is_not_null());
}
} }
} }
@ -295,10 +298,6 @@ fn queries<'a>() -> Queries<
if options.saved_only { if options.saved_only {
query = query.filter(post_saved::id.is_not_null()); query = query.filter(post_saved::id.is_not_null());
} }
if options.moderator_view {
query = query.filter(community_moderator::person_id.is_not_null());
}
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
// setting wont be able to see saved posts. // setting wont be able to see saved posts.
else if !options else if !options
@ -318,16 +317,17 @@ fn queries<'a>() -> Queries<
query = query.filter(post_like::score.eq(-1)); query = query.filter(post_like::score.eq(-1));
} }
if options.local_user.is_some() { // Dont filter blocks or missing languages for moderator view type
if options.local_user.is_some()
&& options.listing_type.unwrap_or_default() != ListingType::ModeratorView
{
// Filter out the rows with missing languages // Filter out the rows with missing languages
query = query.filter(local_user_language::language_id.is_not_null()); query = query.filter(local_user_language::language_id.is_not_null());
// Don't show blocked communities or persons // Don't show blocked communities or persons
query = query.filter(community_block::person_id.is_null()); query = query.filter(community_block::person_id.is_null());
if !options.moderator_view {
query = query.filter(person_block::person_id.is_null()); query = query.filter(person_block::person_id.is_null());
} }
}
let now = diesel::dsl::now.into_sql::<Timestamptz>(); let now = diesel::dsl::now.into_sql::<Timestamptz>();
query = match options.sort.unwrap_or(SortType::Hot) { query = match options.sort.unwrap_or(SortType::Hot) {

View file

@ -0,0 +1,49 @@
ALTER TABLE local_user
ALTER default_listing_type DROP DEFAULT;
ALTER TABLE local_site
ALTER default_post_listing_type DROP DEFAULT;
UPDATE
local_user
SET
default_listing_type = 'Local'
WHERE
default_listing_type = 'ModeratorView';
UPDATE
local_site
SET
default_post_listing_type = 'Local'
WHERE
default_post_listing_type = 'ModeratorView';
-- rename the old enum
ALTER TYPE listing_type_enum RENAME TO listing_type_enum__;
-- create the new enum
CREATE TYPE listing_type_enum AS ENUM (
'All',
'Local',
'Subscribed'
);
-- alter all your enum columns
ALTER TABLE local_user
ALTER COLUMN default_listing_type TYPE listing_type_enum
USING default_listing_type::text::listing_type_enum;
ALTER TABLE local_site
ALTER COLUMN default_post_listing_type TYPE listing_type_enum
USING default_post_listing_type::text::listing_type_enum;
-- Add back in the default
ALTER TABLE local_user
ALTER default_listing_type SET DEFAULT 'Local';
ALTER TABLE local_site
ALTER default_post_listing_type SET DEFAULT 'Local';
-- drop the old enum
DROP TYPE listing_type_enum__;

View file

@ -0,0 +1,4 @@
-- Update the listing_type_enum
ALTER TYPE listing_type_enum
ADD VALUE 'ModeratorView';