mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-26 11:21:02 +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::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
newtypes::{CommentId, CommunityId, CommunityPostTagId, DbUrl, LanguageId, PostId, PostReportId},
|
||||||
ListingType,
|
ListingType,
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
SortType,
|
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 comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod community_post_tag;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
pub mod email_verification;
|
pub mod email_verification;
|
||||||
pub mod federation_allowlist;
|
pub mod federation_allowlist;
|
||||||
|
|
|
@ -284,7 +284,6 @@ impl InstanceId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
|
|
@ -1,26 +1,48 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::{CommunityId, CommunityPostTagId, DbUrl, PostId},
|
||||||
|
schema::{community_post_tag, post_community_post_tag},
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::newtypes::{CommunityId, CommunityPostTagId};
|
|
||||||
|
|
||||||
/// A tag that can be assigned to a post within a community.
|
/// A tag that can be assigned to a post within a community.
|
||||||
/// The tag object is created by the community moderators.
|
/// The tag object is created by the community moderators.
|
||||||
/// The assignment happens by the post creator and can be updated by the community moderators.
|
/// The assignment happens by the post creator and can be updated by the community moderators.
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[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", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
pub struct CommunityPostTag {
|
pub struct CommunityPostTag {
|
||||||
pub id: CommunityPostTagId,
|
pub id: CommunityPostTagId,
|
||||||
pub ap_id: String,
|
pub ap_id: DbUrl,
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
pub updated: Option<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 comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod community_post_tag;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
pub mod custom_emoji_keyword;
|
pub mod custom_emoji_keyword;
|
||||||
pub mod email_verification;
|
pub mod email_verification;
|
||||||
|
@ -39,7 +40,6 @@ pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod tagline;
|
pub mod tagline;
|
||||||
pub mod community_post_tag;
|
|
||||||
|
|
||||||
/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip).
|
/// 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_follower,
|
||||||
community_moderator,
|
community_moderator,
|
||||||
community_person_ban,
|
community_person_ban,
|
||||||
|
community_post_tag,
|
||||||
image_details,
|
image_details,
|
||||||
instance_block,
|
instance_block,
|
||||||
local_user,
|
local_user,
|
||||||
|
@ -37,17 +38,15 @@ use lemmy_db_schema::{
|
||||||
person_post_aggregates,
|
person_post_aggregates,
|
||||||
post,
|
post,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
|
post_community_post_tag,
|
||||||
post_hide,
|
post_hide,
|
||||||
post_like,
|
post_like,
|
||||||
post_read,
|
post_read,
|
||||||
post_saved,
|
post_saved,
|
||||||
post_community_post_tag,
|
|
||||||
community_post_tag
|
|
||||||
},
|
},
|
||||||
source::{local_user::LocalUser, site::Site},
|
source::{local_user::LocalUser, site::Site},
|
||||||
utils::{
|
utils::{
|
||||||
functions::coalesce,
|
functions::coalesce,
|
||||||
functions::json_agg,
|
|
||||||
fuzzy_search,
|
fuzzy_search,
|
||||||
get_conn,
|
get_conn,
|
||||||
limit_and_offset,
|
limit_and_offset,
|
||||||
|
@ -217,11 +216,28 @@ fn queries<'a>() -> Queries<
|
||||||
} else {
|
} else {
|
||||||
Box::new(None::<i64>.into_sql::<sql_types::Nullable<sql_types::BigInt>>())
|
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(
|
// 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
|
post_community_post_tag::table
|
||||||
.inner_join(community_post_tag::table)
|
.inner_join(community_post_tag::table)
|
||||||
.select(diesel::dsl::sql::<diesel::sql_types::Json>("json_agg(community_post_tag.*)"))
|
.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))
|
.filter(post_community_post_tag::post_id.eq(post_aggregates::post_id))
|
||||||
.single_value(),
|
.single_value(),
|
||||||
);
|
);
|
||||||
|
@ -258,7 +274,7 @@ fn queries<'a>() -> Queries<
|
||||||
post_aggregates::comments.nullable() - read_comments,
|
post_aggregates::comments.nullable() - read_comments,
|
||||||
post_aggregates::comments,
|
post_aggregates::comments,
|
||||||
),
|
),
|
||||||
community_post_tags
|
community_post_tags,
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -741,11 +757,12 @@ impl<'a> PostQuery<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::indexing_slicing)]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
post_view::{PaginationCursorData, PostQuery, PostView},
|
post_view::{PaginationCursorData, PostQuery, PostView},
|
||||||
structs::LocalUserView,
|
structs::{LocalUserView, PostCommunityPostTags},
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -765,6 +782,11 @@ mod tests {
|
||||||
CommunityUpdateForm,
|
CommunityUpdateForm,
|
||||||
},
|
},
|
||||||
community_block::{CommunityBlock, CommunityBlockForm},
|
community_block::{CommunityBlock, CommunityBlockForm},
|
||||||
|
community_post_tag::{
|
||||||
|
CommunityPostTag,
|
||||||
|
CommunityPostTagInsertForm,
|
||||||
|
PostCommunityPostTagInsertForm,
|
||||||
|
},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||||
language::Language,
|
language::Language,
|
||||||
|
@ -790,6 +812,7 @@ mod tests {
|
||||||
const POST_BY_BLOCKED_PERSON: &str = "post by blocked person";
|
const POST_BY_BLOCKED_PERSON: &str = "post by blocked person";
|
||||||
const POST_BY_BOT: &str = "post by bot";
|
const POST_BY_BOT: &str = "post by bot";
|
||||||
const POST: &str = "post";
|
const POST: &str = "post";
|
||||||
|
const POST_WITH_TAGS: &str = "post with tags";
|
||||||
|
|
||||||
fn names(post_views: &[PostView]) -> Vec<&str> {
|
fn names(post_views: &[PostView]) -> Vec<&str> {
|
||||||
post_views.iter().map(|i| i.post.name.as_str()).collect()
|
post_views.iter().map(|i| i.post.name.as_str()).collect()
|
||||||
|
@ -803,6 +826,9 @@ mod tests {
|
||||||
inserted_community: Community,
|
inserted_community: Community,
|
||||||
inserted_post: Post,
|
inserted_post: Post,
|
||||||
inserted_bot_post: Post,
|
inserted_bot_post: Post,
|
||||||
|
inserted_post_with_tags: Post,
|
||||||
|
tag_1: CommunityPostTag,
|
||||||
|
tag_2: CommunityPostTag,
|
||||||
site: Site,
|
site: Site,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -874,6 +900,36 @@ mod tests {
|
||||||
|
|
||||||
PersonBlock::block(pool, &person_block).await?;
|
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
|
// A sample post
|
||||||
let new_post = PostInsertForm::builder()
|
let new_post = PostInsertForm::builder()
|
||||||
.name(POST.to_string())
|
.name(POST.to_string())
|
||||||
|
@ -891,6 +947,28 @@ mod tests {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let inserted_bot_post = Post::create(pool, &new_bot_post).await?;
|
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 {
|
let local_user_view = LocalUserView {
|
||||||
local_user: inserted_local_user,
|
local_user: inserted_local_user,
|
||||||
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
||||||
|
@ -930,6 +1008,9 @@ mod tests {
|
||||||
inserted_community,
|
inserted_community,
|
||||||
inserted_post,
|
inserted_post,
|
||||||
inserted_bot_post,
|
inserted_bot_post,
|
||||||
|
inserted_post_with_tags,
|
||||||
|
tag_1,
|
||||||
|
tag_2,
|
||||||
site,
|
site,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -948,12 +1029,14 @@ mod tests {
|
||||||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||||
data.local_user_view.local_user.show_bot_accounts = false;
|
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),
|
community_id: Some(data.inserted_community.id),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
// remove tags post
|
||||||
|
read_post_listing.remove(0);
|
||||||
|
|
||||||
let post_listing_single_with_person = PostView::read(
|
let post_listing_single_with_person = PostView::read(
|
||||||
pool,
|
pool,
|
||||||
|
@ -990,7 +1073,10 @@ mod tests {
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
// should include bot post which has "undetermined" language
|
// 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
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
@ -1019,13 +1105,13 @@ mod tests {
|
||||||
|
|
||||||
// Should be 2 posts, with the bot post, and the blocked
|
// Should be 2 posts, with the bot post, and the blocked
|
||||||
assert_eq!(
|
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)
|
names(&read_post_listing_multiple_no_person)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(&expected_post_listing_no_person),
|
Some(&expected_post_listing_no_person),
|
||||||
read_post_listing_multiple_no_person.get(1)
|
read_post_listing_multiple_no_person.get(2)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_post_listing_no_person,
|
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?;
|
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||||
data.local_user_view.local_user.show_bot_accounts = false;
|
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),
|
community_id: Some(data.inserted_community.id),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
read_post_listing.remove(0);
|
||||||
assert_eq!(vec![expected_post_with_upvote], read_post_listing);
|
assert_eq!(vec![expected_post_with_upvote], read_post_listing);
|
||||||
|
|
||||||
let like_removed =
|
let like_removed =
|
||||||
|
@ -1196,6 +1283,7 @@ mod tests {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let expected_post_listing = vec![
|
let expected_post_listing = vec![
|
||||||
|
("tegan".to_owned(), true, true),
|
||||||
("mybot".to_owned(), false, false),
|
("mybot".to_owned(), false, false),
|
||||||
("tegan".to_owned(), true, true),
|
("tegan".to_owned(), true, true),
|
||||||
];
|
];
|
||||||
|
@ -1234,17 +1322,23 @@ mod tests {
|
||||||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||||
|
|
||||||
// no language filters specified, all posts should be returned
|
// 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?;
|
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?;
|
let post_listing_french = data.default_post_query().list(&data.site, pool).await?;
|
||||||
|
|
||||||
// only one post in french and one undetermined should be returned
|
// 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!(
|
assert_eq!(
|
||||||
Some(french_id),
|
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(
|
LocalUserLanguage::update(
|
||||||
|
@ -1261,6 +1355,7 @@ mod tests {
|
||||||
.map(|p| (p.post.name, p.post.language_id))
|
.map(|p| (p.post.name, p.post.language_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let expected_post_listings_french_und = vec![
|
let expected_post_listings_french_und = vec![
|
||||||
|
(POST_WITH_TAGS.to_owned(), french_id),
|
||||||
(POST_BY_BOT.to_owned(), UNDETERMINED_ID),
|
(POST_BY_BOT.to_owned(), UNDETERMINED_ID),
|
||||||
(POST.to_owned(), french_id),
|
(POST.to_owned(), french_id),
|
||||||
];
|
];
|
||||||
|
@ -1291,7 +1386,7 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the removed post in the results
|
// 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?;
|
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
|
// Removed bot post is shown to admins on its profile page
|
||||||
data.local_user_view.local_user.admin = true;
|
data.local_user_view.local_user.admin = true;
|
||||||
|
@ -1376,7 +1471,12 @@ mod tests {
|
||||||
// no instance block, should return all posts
|
// no instance block, should return all posts
|
||||||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(
|
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)
|
names(&post_listings_all)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1389,7 +1489,10 @@ mod tests {
|
||||||
|
|
||||||
// now posts from communities on that instance should be hidden
|
// now posts from communities on that instance should be hidden
|
||||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
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
|
assert!(post_listings_blocked
|
||||||
.iter()
|
.iter()
|
||||||
.all(|p| p.post.id != post_from_blocked_instance.id));
|
.all(|p| p.post.id != post_from_blocked_instance.id));
|
||||||
|
@ -1398,7 +1501,12 @@ mod tests {
|
||||||
InstanceBlock::unblock(pool, &block_form).await?;
|
InstanceBlock::unblock(pool, &block_form).await?;
|
||||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(
|
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)
|
names(&post_listings_blocked)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1539,7 +1647,7 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the read post in the results
|
// 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?;
|
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
|
// Test with the show_read override as true
|
||||||
let post_listings_show_read_true = PostQuery {
|
let post_listings_show_read_true = PostQuery {
|
||||||
|
@ -1549,7 +1657,7 @@ mod tests {
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_BY_BOT, POST],
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
names(&post_listings_show_read_true)
|
names(&post_listings_show_read_true)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1560,7 +1668,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.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
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1581,7 +1692,10 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the hidden post in the results
|
// 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?;
|
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
|
// Make sure it does come back with the show_hidden option
|
||||||
let post_listings_show_hidden = PostQuery {
|
let post_listings_show_hidden = PostQuery {
|
||||||
|
@ -1592,15 +1706,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.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.
|
// Make sure that hidden field is true.
|
||||||
assert!(
|
assert!(&post_listings_show_hidden[1].hidden);
|
||||||
&post_listings_show_hidden
|
|
||||||
.first()
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?
|
|
||||||
.hidden
|
|
||||||
);
|
|
||||||
|
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
@ -1622,7 +1734,7 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the nsfw post in the regular results
|
// 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?;
|
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
|
// Make sure it does come back with the show_nsfw option
|
||||||
let post_listings_show_nsfw = PostQuery {
|
let post_listings_show_nsfw = PostQuery {
|
||||||
|
@ -1633,16 +1745,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.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.
|
// Make sure that nsfw field is true.
|
||||||
assert!(
|
assert!(&post_listings_show_nsfw[1].post.nsfw);
|
||||||
&post_listings_show_nsfw
|
|
||||||
.first()
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?
|
|
||||||
.post
|
|
||||||
.nsfw
|
|
||||||
);
|
|
||||||
|
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
@ -1776,7 +1885,7 @@ mod tests {
|
||||||
hidden: false,
|
hidden: false,
|
||||||
saved: false,
|
saved: false,
|
||||||
creator_blocked: false,
|
creator_blocked: false,
|
||||||
community_post_tags: None,
|
community_post_tags: PostCommunityPostTags::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1810,7 +1919,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.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?;
|
let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await?;
|
||||||
assert!(unauthenticated_post.is_none());
|
assert!(unauthenticated_post.is_none());
|
||||||
|
@ -1892,4 +2001,32 @@ mod tests {
|
||||||
|
|
||||||
cleanup(data, pool).await
|
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")]
|
#[cfg(feature = "full")]
|
||||||
use diesel::Queryable;
|
use diesel::Queryable;
|
||||||
|
use diesel::{
|
||||||
|
deserialize::{FromSql, FromSqlRow},
|
||||||
|
expression::AsExpression,
|
||||||
|
pg::{Pg, PgValue},
|
||||||
|
serialize::ToSql,
|
||||||
|
sql_types::{self, Nullable},
|
||||||
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
||||||
source::{
|
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,
|
SubscribedType,
|
||||||
};
|
};
|
||||||
|
@ -128,7 +152,7 @@ pub struct PostView {
|
||||||
pub creator_blocked: bool,
|
pub creator_blocked: bool,
|
||||||
pub my_vote: Option<i16>,
|
pub my_vote: Option<i16>,
|
||||||
pub unread_comments: i64,
|
pub unread_comments: i64,
|
||||||
pub community_post_tags: Option<serde_json::Value>
|
pub community_post_tags: PostCommunityPostTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||||
|
@ -213,3 +237,42 @@ pub struct LocalImageView {
|
||||||
pub local_image: LocalImage,
|
pub local_image: LocalImage,
|
||||||
pub person: Person,
|
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`
|
-- This file should undo anything in `up.sql`
|
||||||
drop table post_community_post_tag;
|
DROP TABLE post_community_post_tag;
|
||||||
drop table 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
|
-- a tag for a post, valid in a community. created by mods of a community
|
||||||
CREATE TABLE community_post_tag (
|
CREATE TABLE community_post_tag (
|
||||||
id SERIAL PRIMARY KEY,
|
id serial PRIMARY KEY,
|
||||||
ap_id TEXT NOT NULL UNIQUE,
|
ap_id text NOT NULL UNIQUE,
|
||||||
community_id INT NOT NULL REFERENCES community(id),
|
community_id int NOT NULL REFERENCES community (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
name TEXT NOT NULL,
|
name text NOT NULL,
|
||||||
published TIMESTAMPTZ NOT NULL,
|
published timestamptz NOT NULL DEFAULT now(),
|
||||||
updated TIMESTAMPTZ,
|
updated timestamptz,
|
||||||
deleted TIMESTAMPTZ
|
deleted timestamptz
|
||||||
);
|
);
|
||||||
|
|
||||||
-- an association between a post and a community post tag. created/updated by the post author or mods of a community
|
-- 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 (
|
CREATE TABLE post_community_post_tag (
|
||||||
post_id INT NOT NULL REFERENCES post(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),
|
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)
|
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("/follow", web::post().to(follow_community))
|
||||||
.route("/block", web::post().to(block_community))
|
.route("/block", web::post().to(block_community))
|
||||||
.route("/delete", web::post().to(delete_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
|
// 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("/remove", web::post().to(remove_community))
|
||||||
.route("/transfer", web::post().to(transfer_community))
|
.route("/transfer", web::post().to(transfer_community))
|
||||||
.route("/ban_user", web::post().to(ban_from_community))
|
.route("/ban_user", web::post().to(ban_from_community))
|
||||||
.route("/mod", web::post().to(add_mod_to_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)),
|
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::scope("/federated_instances")
|
web::scope("/federated_instances")
|
||||||
|
|
Loading…
Reference in a new issue