mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 03:11:32 +00:00
fixes
This commit is contained in:
parent
003613b736
commit
c130bee420
11 changed files with 359 additions and 75 deletions
|
@ -1,5 +1,5 @@
|
|||
use lemmy_db_schema::{
|
||||
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
||||
newtypes::{CommentId, CommunityId, CommunityPostTagId, DbUrl, LanguageId, PostId, PostReportId},
|
||||
ListingType,
|
||||
PostFeatureType,
|
||||
SortType,
|
||||
|
|
59
crates/db_schema/src/impls/community_post_tag.rs
Normal file
59
crates/db_schema/src/impls/community_post_tag.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use crate::{
|
||||
newtypes::CommunityPostTagId,
|
||||
schema::{community_post_tag, post_community_post_tag},
|
||||
source::community_post_tag::{
|
||||
CommunityPostTag,
|
||||
CommunityPostTagInsertForm,
|
||||
PostCommunityPostTagInsertForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use diesel::{insert_into, result::Error, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for CommunityPostTag {
|
||||
type InsertForm = CommunityPostTagInsertForm;
|
||||
|
||||
type UpdateForm = CommunityPostTagInsertForm;
|
||||
|
||||
type IdType = CommunityPostTagId;
|
||||
|
||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(community_post_tag::table)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
pid: CommunityPostTagId,
|
||||
form: &Self::UpdateForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(community_post_tag::table.find(pid))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PostCommunityPostTagInsertForm {
|
||||
pub async fn insert_tag_associations(
|
||||
pool: &mut DbPool<'_>,
|
||||
tags: &[PostCommunityPostTagInsertForm],
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(post_community_post_tag::table)
|
||||
.values(tags)
|
||||
.execute(conn)
|
||||
.await
|
||||
.context("Failed to insert post community tag associations")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ pub mod comment_reply;
|
|||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_block;
|
||||
pub mod community_post_tag;
|
||||
pub mod custom_emoji;
|
||||
pub mod email_verification;
|
||||
pub mod federation_allowlist;
|
||||
|
|
|
@ -284,9 +284,8 @@ impl InstanceId {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The post id.
|
||||
pub struct CommunityPostTagId(pub i32);
|
||||
pub struct CommunityPostTagId(pub i32);
|
||||
|
|
|
@ -1,26 +1,48 @@
|
|||
use crate::{
|
||||
newtypes::{CommunityId, CommunityPostTagId, DbUrl, PostId},
|
||||
schema::{community_post_tag, post_community_post_tag},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::newtypes::{CommunityId, CommunityPostTagId};
|
||||
|
||||
/// A tag that can be assigned to a post within a community.
|
||||
/// The tag object is created by the community moderators.
|
||||
/// The assignment happens by the post creator and can be updated by the community moderators.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable, Identifiable))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_post_tag))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct CommunityPostTag {
|
||||
pub id: CommunityPostTagId,
|
||||
pub ap_id: String,
|
||||
pub ap_id: DbUrl,
|
||||
pub community_id: CommunityId,
|
||||
pub name: String,
|
||||
pub published: DateTime<Utc>,
|
||||
pub updated: Option<DateTime<Utc>>,
|
||||
pub deleted: Option<DateTime<Utc>>
|
||||
pub deleted: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = community_post_tag))]
|
||||
pub struct CommunityPostTagInsertForm {
|
||||
pub ap_id: DbUrl,
|
||||
pub community_id: CommunityId,
|
||||
pub name: String,
|
||||
// default now
|
||||
pub published: Option<DateTime<Utc>>,
|
||||
pub updated: Option<DateTime<Utc>>,
|
||||
pub deleted: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = post_community_post_tag))]
|
||||
pub struct PostCommunityPostTagInsertForm {
|
||||
pub post_id: PostId,
|
||||
pub community_post_tag_id: CommunityPostTagId,
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ pub mod comment_reply;
|
|||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_block;
|
||||
pub mod community_post_tag;
|
||||
pub mod custom_emoji;
|
||||
pub mod custom_emoji_keyword;
|
||||
pub mod email_verification;
|
||||
|
@ -39,7 +40,6 @@ pub mod registration_application;
|
|||
pub mod secret;
|
||||
pub mod site;
|
||||
pub mod tagline;
|
||||
pub mod community_post_tag;
|
||||
|
||||
/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip).
|
||||
///
|
||||
|
|
|
@ -28,6 +28,7 @@ use lemmy_db_schema::{
|
|||
community_follower,
|
||||
community_moderator,
|
||||
community_person_ban,
|
||||
community_post_tag,
|
||||
image_details,
|
||||
instance_block,
|
||||
local_user,
|
||||
|
@ -37,17 +38,15 @@ use lemmy_db_schema::{
|
|||
person_post_aggregates,
|
||||
post,
|
||||
post_aggregates,
|
||||
post_community_post_tag,
|
||||
post_hide,
|
||||
post_like,
|
||||
post_read,
|
||||
post_saved,
|
||||
post_community_post_tag,
|
||||
community_post_tag
|
||||
},
|
||||
source::{local_user::LocalUser, site::Site},
|
||||
utils::{
|
||||
functions::coalesce,
|
||||
functions::json_agg,
|
||||
fuzzy_search,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
|
@ -217,14 +216,31 @@ fn queries<'a>() -> Queries<
|
|||
} else {
|
||||
Box::new(None::<i64>.into_sql::<sql_types::Nullable<sql_types::BigInt>>())
|
||||
};
|
||||
let community_post_tags: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Json>>> =
|
||||
Box::new(
|
||||
post_community_post_tag::table
|
||||
.inner_join(community_post_tag::table)
|
||||
.select(diesel::dsl::sql::<diesel::sql_types::Json>("json_agg(community_post_tag.*)"))
|
||||
.filter(post_community_post_tag::post_id.eq(post_aggregates::post_id))
|
||||
.single_value(),
|
||||
);
|
||||
|
||||
// We fetch post tags by letting postgresql aggregate them internally in a subquery into JSON.
|
||||
// This is a simple way to join m rows into n rows without duplicating the data and getting
|
||||
// complex diesel types. In pure SQL you would usually do this either using a LEFT JOIN + then
|
||||
// aggregating the results in the application code. But this results in a lot of duplicate
|
||||
// data transferred (since each post will be returned once per tag that it has) and more
|
||||
// complicated application code. The diesel docs suggest doing three separate sequential queries
|
||||
// in this case (see https://diesel.rs/guides/relations.html#many-to-many-or-mn ): First fetch
|
||||
// the posts, then fetch all relevant post-tag-association tuples from the db, and then fetch
|
||||
// all the relevant tag objects.
|
||||
//
|
||||
// If we want to filter by post tag we will have to add
|
||||
// separate logic below since this subquery can't affect filtering, but it is simple (`WHERE
|
||||
// exists (select 1 from post_community_post_tags where community_post_tag_id in (1,2,3,4)`).
|
||||
let community_post_tags: Box<
|
||||
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Json>>,
|
||||
> = Box::new(
|
||||
post_community_post_tag::table
|
||||
.inner_join(community_post_tag::table)
|
||||
.select(diesel::dsl::sql::<diesel::sql_types::Json>(
|
||||
"json_agg(community_post_tag.*)",
|
||||
))
|
||||
.filter(post_community_post_tag::post_id.eq(post_aggregates::post_id))
|
||||
.single_value(),
|
||||
);
|
||||
|
||||
query
|
||||
.inner_join(person::table)
|
||||
|
@ -258,7 +274,7 @@ fn queries<'a>() -> Queries<
|
|||
post_aggregates::comments.nullable() - read_comments,
|
||||
post_aggregates::comments,
|
||||
),
|
||||
community_post_tags
|
||||
community_post_tags,
|
||||
))
|
||||
};
|
||||
|
||||
|
@ -741,11 +757,12 @@ impl<'a> PostQuery<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
post_view::{PaginationCursorData, PostQuery, PostView},
|
||||
structs::LocalUserView,
|
||||
structs::{LocalUserView, PostCommunityPostTags},
|
||||
};
|
||||
use chrono::Utc;
|
||||
use lemmy_db_schema::{
|
||||
|
@ -765,6 +782,11 @@ mod tests {
|
|||
CommunityUpdateForm,
|
||||
},
|
||||
community_block::{CommunityBlock, CommunityBlockForm},
|
||||
community_post_tag::{
|
||||
CommunityPostTag,
|
||||
CommunityPostTagInsertForm,
|
||||
PostCommunityPostTagInsertForm,
|
||||
},
|
||||
instance::Instance,
|
||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||
language::Language,
|
||||
|
@ -790,6 +812,7 @@ mod tests {
|
|||
const POST_BY_BLOCKED_PERSON: &str = "post by blocked person";
|
||||
const POST_BY_BOT: &str = "post by bot";
|
||||
const POST: &str = "post";
|
||||
const POST_WITH_TAGS: &str = "post with tags";
|
||||
|
||||
fn names(post_views: &[PostView]) -> Vec<&str> {
|
||||
post_views.iter().map(|i| i.post.name.as_str()).collect()
|
||||
|
@ -803,6 +826,9 @@ mod tests {
|
|||
inserted_community: Community,
|
||||
inserted_post: Post,
|
||||
inserted_bot_post: Post,
|
||||
inserted_post_with_tags: Post,
|
||||
tag_1: CommunityPostTag,
|
||||
tag_2: CommunityPostTag,
|
||||
site: Site,
|
||||
}
|
||||
|
||||
|
@ -874,6 +900,36 @@ mod tests {
|
|||
|
||||
PersonBlock::block(pool, &person_block).await?;
|
||||
|
||||
// Two community post tags
|
||||
let tag_1 = CommunityPostTag::create(
|
||||
pool,
|
||||
&CommunityPostTagInsertForm {
|
||||
ap_id: Url::parse(&format!("{}/tags/test_tag1", inserted_community.actor_id))
|
||||
.expect("valid")
|
||||
.into(),
|
||||
community_id: inserted_community.id,
|
||||
name: "Test Tag 1".into(),
|
||||
published: None,
|
||||
updated: None,
|
||||
deleted: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let tag_2 = CommunityPostTag::create(
|
||||
pool,
|
||||
&CommunityPostTagInsertForm {
|
||||
ap_id: Url::parse(&format!("{}/tags/test_tag2", inserted_community.actor_id))
|
||||
.expect("valid")
|
||||
.into(),
|
||||
community_id: inserted_community.id,
|
||||
name: "Test Tag 2".into(),
|
||||
published: None,
|
||||
updated: None,
|
||||
deleted: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// A sample post
|
||||
let new_post = PostInsertForm::builder()
|
||||
.name(POST.to_string())
|
||||
|
@ -891,6 +947,28 @@ mod tests {
|
|||
.build();
|
||||
|
||||
let inserted_bot_post = Post::create(pool, &new_bot_post).await?;
|
||||
|
||||
// A sample post with tags
|
||||
let new_post = PostInsertForm::builder()
|
||||
.name(POST_WITH_TAGS.to_string())
|
||||
.creator_id(inserted_person.id)
|
||||
.community_id(inserted_community.id)
|
||||
.language_id(Some(LanguageId(47)))
|
||||
.build();
|
||||
|
||||
let inserted_post_with_tags = Post::create(pool, &new_post).await?;
|
||||
let inserted_tags = vec![
|
||||
PostCommunityPostTagInsertForm {
|
||||
post_id: inserted_post_with_tags.id,
|
||||
community_post_tag_id: tag_1.id,
|
||||
},
|
||||
PostCommunityPostTagInsertForm {
|
||||
post_id: inserted_post_with_tags.id,
|
||||
community_post_tag_id: tag_2.id,
|
||||
},
|
||||
];
|
||||
PostCommunityPostTagInsertForm::insert_tag_associations(pool, &inserted_tags).await?;
|
||||
|
||||
let local_user_view = LocalUserView {
|
||||
local_user: inserted_local_user,
|
||||
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
||||
|
@ -930,6 +1008,9 @@ mod tests {
|
|||
inserted_community,
|
||||
inserted_post,
|
||||
inserted_bot_post,
|
||||
inserted_post_with_tags,
|
||||
tag_1,
|
||||
tag_2,
|
||||
site,
|
||||
})
|
||||
}
|
||||
|
@ -948,12 +1029,14 @@ mod tests {
|
|||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||
data.local_user_view.local_user.show_bot_accounts = false;
|
||||
|
||||
let read_post_listing = PostQuery {
|
||||
let mut read_post_listing = PostQuery {
|
||||
community_id: Some(data.inserted_community.id),
|
||||
..data.default_post_query()
|
||||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
// remove tags post
|
||||
read_post_listing.remove(0);
|
||||
|
||||
let post_listing_single_with_person = PostView::read(
|
||||
pool,
|
||||
|
@ -990,7 +1073,10 @@ mod tests {
|
|||
.list(&data.site, pool)
|
||||
.await?;
|
||||
// should include bot post which has "undetermined" language
|
||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_with_bots));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_with_bots)
|
||||
);
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
@ -1019,13 +1105,13 @@ mod tests {
|
|||
|
||||
// Should be 2 posts, with the bot post, and the blocked
|
||||
assert_eq!(
|
||||
vec![POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
||||
names(&read_post_listing_multiple_no_person)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Some(&expected_post_listing_no_person),
|
||||
read_post_listing_multiple_no_person.get(1)
|
||||
read_post_listing_multiple_no_person.get(2)
|
||||
);
|
||||
assert_eq!(
|
||||
expected_post_listing_no_person,
|
||||
|
@ -1106,12 +1192,13 @@ mod tests {
|
|||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||
data.local_user_view.local_user.show_bot_accounts = false;
|
||||
|
||||
let read_post_listing = PostQuery {
|
||||
let mut read_post_listing = PostQuery {
|
||||
community_id: Some(data.inserted_community.id),
|
||||
..data.default_post_query()
|
||||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
read_post_listing.remove(0);
|
||||
assert_eq!(vec![expected_post_with_upvote], read_post_listing);
|
||||
|
||||
let like_removed =
|
||||
|
@ -1196,6 +1283,7 @@ mod tests {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let expected_post_listing = vec![
|
||||
("tegan".to_owned(), true, true),
|
||||
("mybot".to_owned(), false, false),
|
||||
("tegan".to_owned(), true, true),
|
||||
];
|
||||
|
@ -1234,17 +1322,23 @@ mod tests {
|
|||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||
|
||||
// no language filters specified, all posts should be returned
|
||||
assert_eq!(vec![EL_POSTO, POST_BY_BOT, POST], names(&post_listings_all));
|
||||
assert_eq!(
|
||||
vec![EL_POSTO, POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_all)
|
||||
);
|
||||
|
||||
LocalUserLanguage::update(pool, vec![french_id], data.local_user_view.local_user.id).await?;
|
||||
|
||||
let post_listing_french = data.default_post_query().list(&data.site, pool).await?;
|
||||
|
||||
// only one post in french and one undetermined should be returned
|
||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listing_french));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listing_french)
|
||||
);
|
||||
assert_eq!(
|
||||
Some(french_id),
|
||||
post_listing_french.get(1).map(|p| p.post.language_id)
|
||||
post_listing_french.get(2).map(|p| p.post.language_id)
|
||||
);
|
||||
|
||||
LocalUserLanguage::update(
|
||||
|
@ -1261,6 +1355,7 @@ mod tests {
|
|||
.map(|p| (p.post.name, p.post.language_id))
|
||||
.collect::<Vec<_>>();
|
||||
let expected_post_listings_french_und = vec![
|
||||
(POST_WITH_TAGS.to_owned(), french_id),
|
||||
(POST_BY_BOT.to_owned(), UNDETERMINED_ID),
|
||||
(POST.to_owned(), french_id),
|
||||
];
|
||||
|
@ -1291,7 +1386,7 @@ mod tests {
|
|||
|
||||
// Make sure you don't see the removed post in the results
|
||||
let post_listings_no_admin = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(vec![POST], names(&post_listings_no_admin));
|
||||
assert_eq!(vec![POST_WITH_TAGS, POST], names(&post_listings_no_admin));
|
||||
|
||||
// Removed bot post is shown to admins on its profile page
|
||||
data.local_user_view.local_user.admin = true;
|
||||
|
@ -1376,7 +1471,12 @@ mod tests {
|
|||
// no instance block, should return all posts
|
||||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(
|
||||
vec![POST_FROM_BLOCKED_INSTANCE, POST_BY_BOT, POST],
|
||||
vec![
|
||||
POST_FROM_BLOCKED_INSTANCE,
|
||||
POST_WITH_TAGS,
|
||||
POST_BY_BOT,
|
||||
POST
|
||||
],
|
||||
names(&post_listings_all)
|
||||
);
|
||||
|
||||
|
@ -1389,7 +1489,10 @@ mod tests {
|
|||
|
||||
// now posts from communities on that instance should be hidden
|
||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_blocked));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_blocked)
|
||||
);
|
||||
assert!(post_listings_blocked
|
||||
.iter()
|
||||
.all(|p| p.post.id != post_from_blocked_instance.id));
|
||||
|
@ -1398,7 +1501,12 @@ mod tests {
|
|||
InstanceBlock::unblock(pool, &block_form).await?;
|
||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(
|
||||
vec![POST_FROM_BLOCKED_INSTANCE, POST_BY_BOT, POST],
|
||||
vec![
|
||||
POST_FROM_BLOCKED_INSTANCE,
|
||||
POST_WITH_TAGS,
|
||||
POST_BY_BOT,
|
||||
POST
|
||||
],
|
||||
names(&post_listings_blocked)
|
||||
);
|
||||
|
||||
|
@ -1539,7 +1647,7 @@ mod tests {
|
|||
|
||||
// Make sure you don't see the read post in the results
|
||||
let post_listings_hide_read = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(vec![POST], names(&post_listings_hide_read));
|
||||
assert_eq!(vec![POST_WITH_TAGS, POST], names(&post_listings_hide_read));
|
||||
|
||||
// Test with the show_read override as true
|
||||
let post_listings_show_read_true = PostQuery {
|
||||
|
@ -1549,7 +1657,7 @@ mod tests {
|
|||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
vec![POST_BY_BOT, POST],
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_show_read_true)
|
||||
);
|
||||
|
||||
|
@ -1560,7 +1668,10 @@ mod tests {
|
|||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(vec![POST], names(&post_listings_show_read_false));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST],
|
||||
names(&post_listings_show_read_false)
|
||||
);
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
|
@ -1581,7 +1692,10 @@ mod tests {
|
|||
|
||||
// Make sure you don't see the hidden post in the results
|
||||
let post_listings_hide_hidden = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(vec![POST], names(&post_listings_hide_hidden));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST],
|
||||
names(&post_listings_hide_hidden)
|
||||
);
|
||||
|
||||
// Make sure it does come back with the show_hidden option
|
||||
let post_listings_show_hidden = PostQuery {
|
||||
|
@ -1592,15 +1706,13 @@ mod tests {
|
|||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_hidden));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_show_hidden)
|
||||
);
|
||||
|
||||
// Make sure that hidden field is true.
|
||||
assert!(
|
||||
&post_listings_show_hidden
|
||||
.first()
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?
|
||||
.hidden
|
||||
);
|
||||
assert!(&post_listings_show_hidden[1].hidden);
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
@ -1622,7 +1734,7 @@ mod tests {
|
|||
|
||||
// Make sure you don't see the nsfw post in the regular results
|
||||
let post_listings_hide_nsfw = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(vec![POST], names(&post_listings_hide_nsfw));
|
||||
assert_eq!(vec![POST_WITH_TAGS, POST], names(&post_listings_hide_nsfw));
|
||||
|
||||
// Make sure it does come back with the show_nsfw option
|
||||
let post_listings_show_nsfw = PostQuery {
|
||||
|
@ -1633,16 +1745,13 @@ mod tests {
|
|||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_nsfw));
|
||||
assert_eq!(
|
||||
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||
names(&post_listings_show_nsfw)
|
||||
);
|
||||
|
||||
// Make sure that nsfw field is true.
|
||||
assert!(
|
||||
&post_listings_show_nsfw
|
||||
.first()
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?
|
||||
.post
|
||||
.nsfw
|
||||
);
|
||||
assert!(&post_listings_show_nsfw[1].post.nsfw);
|
||||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
@ -1776,7 +1885,7 @@ mod tests {
|
|||
hidden: false,
|
||||
saved: false,
|
||||
creator_blocked: false,
|
||||
community_post_tags: None,
|
||||
community_post_tags: PostCommunityPostTags::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1810,7 +1919,7 @@ mod tests {
|
|||
}
|
||||
.list(&data.site, pool)
|
||||
.await?;
|
||||
assert_eq!(2, authenticated_query.len());
|
||||
assert_eq!(3, authenticated_query.len());
|
||||
|
||||
let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await?;
|
||||
assert!(unauthenticated_post.is_none());
|
||||
|
@ -1892,4 +2001,32 @@ mod tests {
|
|||
|
||||
cleanup(data, pool).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn post_tags_present() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
let post_view = PostView::read(
|
||||
pool,
|
||||
data.inserted_post_with_tags.id,
|
||||
Some(&data.local_user_view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
assert_eq!(2, post_view.community_post_tags.tags.len());
|
||||
assert_eq!(data.tag_1.name, post_view.community_post_tags.tags[0].name);
|
||||
assert_eq!(data.tag_2.name, post_view.community_post_tags.tags[1].name);
|
||||
|
||||
let all_posts = data.default_post_query().list(&data.site, pool).await?;
|
||||
assert_eq!(2, all_posts[0].community_post_tags.tags.len()); // post with tags
|
||||
assert_eq!(0, all_posts[1].community_post_tags.tags.len()); // bot post
|
||||
assert_eq!(0, all_posts[2].community_post_tags.tags.len()); // normal post
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,33 @@
|
|||
#[cfg(feature = "full")]
|
||||
use diesel::Queryable;
|
||||
use diesel::{
|
||||
deserialize::{FromSql, FromSqlRow},
|
||||
expression::AsExpression,
|
||||
pg::{Pg, PgValue},
|
||||
serialize::ToSql,
|
||||
sql_types::{self, Nullable},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
||||
source::{
|
||||
comment::Comment, comment_report::CommentReport, community::Community, community_post_tag::CommunityPostTag, custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword, images::{ImageDetails, LocalImage}, local_site::LocalSite, local_site_rate_limit::LocalSiteRateLimit, local_user::LocalUser, local_user_vote_display_mode::LocalUserVoteDisplayMode, person::Person, post::Post, post_report::PostReport, private_message::PrivateMessage, private_message_report::PrivateMessageReport, registration_application::RegistrationApplication, site::Site
|
||||
comment::Comment,
|
||||
comment_report::CommentReport,
|
||||
community::Community,
|
||||
community_post_tag::CommunityPostTag,
|
||||
custom_emoji::CustomEmoji,
|
||||
custom_emoji_keyword::CustomEmojiKeyword,
|
||||
images::{ImageDetails, LocalImage},
|
||||
local_site::LocalSite,
|
||||
local_site_rate_limit::LocalSiteRateLimit,
|
||||
local_user::LocalUser,
|
||||
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
||||
person::Person,
|
||||
post::Post,
|
||||
post_report::PostReport,
|
||||
private_message::PrivateMessage,
|
||||
private_message_report::PrivateMessageReport,
|
||||
registration_application::RegistrationApplication,
|
||||
site::Site,
|
||||
},
|
||||
SubscribedType,
|
||||
};
|
||||
|
@ -128,7 +152,7 @@ pub struct PostView {
|
|||
pub creator_blocked: bool,
|
||||
pub my_vote: Option<i16>,
|
||||
pub unread_comments: i64,
|
||||
pub community_post_tags: Option<serde_json::Value>
|
||||
pub community_post_tags: PostCommunityPostTags,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
|
@ -213,3 +237,42 @@ pub struct LocalImageView {
|
|||
pub local_image: LocalImage,
|
||||
pub person: Person,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
Debug,
|
||||
PartialEq,
|
||||
TS,
|
||||
FromSqlRow,
|
||||
AsExpression,
|
||||
Default,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
#[diesel(sql_type = Nullable<sql_types::Json>)]
|
||||
pub struct PostCommunityPostTags {
|
||||
pub tags: Vec<CommunityPostTag>,
|
||||
}
|
||||
|
||||
impl FromSql<Nullable<sql_types::Json>, Pg> for PostCommunityPostTags {
|
||||
fn from_sql(bytes: PgValue) -> diesel::deserialize::Result<Self> {
|
||||
let value = <serde_json::Value as FromSql<sql_types::Json, Pg>>::from_sql(bytes)?;
|
||||
Ok(serde_json::from_value::<PostCommunityPostTags>(value)?)
|
||||
}
|
||||
fn from_nullable_sql(
|
||||
bytes: Option<<Pg as diesel::backend::Backend>::RawValue<'_>>,
|
||||
) -> diesel::deserialize::Result<Self> {
|
||||
match bytes {
|
||||
Some(bytes) => Self::from_sql(bytes),
|
||||
None => Ok(Self { tags: vec![] }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql<Nullable<sql_types::Json>, Pg> for PostCommunityPostTags {
|
||||
fn to_sql(&self, out: &mut diesel::serialize::Output<Pg>) -> diesel::serialize::Result {
|
||||
let value = serde_json::to_value(self)?;
|
||||
<serde_json::Value as ToSql<sql_types::Json, Pg>>::to_sql(&value, &mut out.reborrow())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
drop table post_community_post_tag;
|
||||
drop table community_post_tag;
|
||||
DROP TABLE post_community_post_tag;
|
||||
|
||||
DROP TABLE community_post_tag;
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
-- a tag for a post, valid in a community. created by mods of a community
|
||||
CREATE TABLE community_post_tag (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ap_id TEXT NOT NULL UNIQUE,
|
||||
community_id INT NOT NULL REFERENCES community(id),
|
||||
name TEXT NOT NULL,
|
||||
published TIMESTAMPTZ NOT NULL,
|
||||
updated TIMESTAMPTZ,
|
||||
deleted TIMESTAMPTZ
|
||||
id serial PRIMARY KEY,
|
||||
ap_id text NOT NULL UNIQUE,
|
||||
community_id int NOT NULL REFERENCES community (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
published timestamptz NOT NULL DEFAULT now(),
|
||||
updated timestamptz,
|
||||
deleted timestamptz
|
||||
);
|
||||
|
||||
-- an association between a post and a community post tag. created/updated by the post author or mods of a community
|
||||
CREATE TABLE post_community_post_tag (
|
||||
post_id INT NOT NULL REFERENCES post(id),
|
||||
community_post_tag_id INT NOT NULL REFERENCES community_post_tag(id),
|
||||
PRIMARY KEY (post_id, community_post_tag_id)
|
||||
post_id int NOT NULL REFERENCES post (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
community_post_tag_id int NOT NULL REFERENCES community_post_tag (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
PRIMARY KEY (post_id, community_post_tag_id)
|
||||
);
|
||||
|
||||
|
|
|
@ -183,14 +183,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
.route("/follow", web::post().to(follow_community))
|
||||
.route("/block", web::post().to(block_community))
|
||||
.route("/delete", web::post().to(delete_community))
|
||||
.route("/post_tags", web::get().to(get_community_post_tags))
|
||||
// .route("/post_tags", web::get().to(get_community_post_tags))
|
||||
// Mod Actions
|
||||
// .route("/post_tags", web::post().to(create_update_community_post_tag))
|
||||
// .route("/post_tags/delete", web::post().to(delete_community_post_tag)),
|
||||
.route("/remove", web::post().to(remove_community))
|
||||
.route("/transfer", web::post().to(transfer_community))
|
||||
.route("/ban_user", web::post().to(ban_from_community))
|
||||
.route("/mod", web::post().to(add_mod_to_community))
|
||||
.route("/post_tags", web::post().to(create_update_community_post_tag))
|
||||
.route("/post_tags/delete", web::post().to(delete_community_post_tag)),
|
||||
.route("/mod", web::post().to(add_mod_to_community)),
|
||||
)
|
||||
.service(
|
||||
web::scope("/federated_instances")
|
||||
|
|
Loading…
Reference in a new issue