-
-
Notifications
You must be signed in to change notification settings - Fork 895
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
Community post tags (part 2: API methods) #5389
base: main
Are you sure you want to change the base?
Changes from 4 commits
d69ccc8
ee045ad
1f4c451
55e71b9
c13feef
5335294
475f2fd
f920ca5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ pub mod follow; | |
pub mod hide; | ||
pub mod pending_follows; | ||
pub mod random; | ||
pub mod tag; | ||
pub mod transfer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
use activitypub_federation::config::Data; | ||
use actix_web::web::Json; | ||
use lemmy_api_common::{ | ||
community::{ | ||
CommunityTagResponse, | ||
CreateCommunityTag, | ||
DeleteCommunityTag, | ||
ListCommunityTags, | ||
ListCommunityTagsResponse, | ||
UpdateCommunityTag, | ||
}, | ||
context::LemmyContext, | ||
utils::check_community_mod_action, | ||
}; | ||
use lemmy_db_schema::{ | ||
source::{ | ||
community::Community, | ||
tag::{Tag, TagInsertForm}, | ||
}, | ||
traits::Crud, | ||
}; | ||
use lemmy_db_views::structs::LocalUserView; | ||
use lemmy_utils::{error::LemmyResult, utils::validation::is_valid_tag_slug}; | ||
use url::Url; | ||
|
||
#[tracing::instrument(skip(context))] | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub async fn create_community_tag( | ||
data: Json<CreateCommunityTag>, | ||
context: Data<LemmyContext>, | ||
local_user_view: LocalUserView, | ||
) -> LemmyResult<Json<CommunityTagResponse>> { | ||
let community = Community::read(&mut context.pool(), data.community_id).await?; | ||
|
||
// Verify that only mods can create tags | ||
check_community_mod_action( | ||
&local_user_view.person, | ||
&community, | ||
false, | ||
&mut context.pool(), | ||
) | ||
.await?; | ||
|
||
is_valid_tag_slug(&data.id_slug)?; | ||
|
||
// Create the tag | ||
let tag_form = TagInsertForm { | ||
name: data.name.clone(), | ||
community_id: data.community_id, | ||
ap_id: Url::parse(&format!("{}/tag/{}", community.actor_id, &data.id_slug))?.into(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add this as a function to the community impl? Then you can do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or better yet a db trigger |
||
published: None, // defaults to now | ||
updated: None, | ||
deleted: false, | ||
}; | ||
|
||
let tag = Tag::create(&mut context.pool(), &tag_form).await?; | ||
|
||
Ok(Json(CommunityTagResponse { | ||
id: tag.id, | ||
name: tag.name, | ||
community_id: tag.community_id, | ||
})) | ||
} | ||
|
||
#[tracing::instrument(skip(context))] | ||
pub async fn update_community_tag( | ||
data: Json<UpdateCommunityTag>, | ||
context: Data<LemmyContext>, | ||
local_user_view: LocalUserView, | ||
) -> LemmyResult<Json<CommunityTagResponse>> { | ||
let tag = Tag::read(&mut context.pool(), data.tag_id).await?; | ||
let community = Community::read(&mut context.pool(), tag.community_id).await?; | ||
|
||
// Verify that only mods can update tags | ||
check_community_mod_action( | ||
&local_user_view.person, | ||
&community, | ||
false, | ||
&mut context.pool(), | ||
) | ||
.await?; | ||
|
||
// Update the tag | ||
let tag_form = TagInsertForm { | ||
name: data.name.clone(), | ||
community_id: tag.community_id, | ||
ap_id: tag.ap_id, | ||
published: None, | ||
updated: Some(chrono::Utc::now()), | ||
deleted: false, | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
let tag = Tag::update(&mut context.pool(), data.tag_id, &tag_form).await?; | ||
|
||
Ok(Json(CommunityTagResponse { | ||
id: tag.id, | ||
name: tag.name, | ||
community_id: tag.community_id, | ||
})) | ||
} | ||
|
||
#[tracing::instrument(skip(context))] | ||
pub async fn delete_community_tag( | ||
data: Json<DeleteCommunityTag>, | ||
context: Data<LemmyContext>, | ||
local_user_view: LocalUserView, | ||
) -> LemmyResult<Json<CommunityTagResponse>> { | ||
let tag = Tag::read(&mut context.pool(), data.tag_id).await?; | ||
let community = Community::read(&mut context.pool(), tag.community_id).await?; | ||
|
||
// Verify that only mods can delete tags | ||
check_community_mod_action( | ||
&local_user_view.person, | ||
&community, | ||
false, | ||
&mut context.pool(), | ||
) | ||
.await?; | ||
|
||
// Soft delete the tag | ||
let tag_form = TagInsertForm { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be a |
||
name: tag.name.clone(), | ||
community_id: tag.community_id, | ||
ap_id: tag.ap_id, | ||
published: None, | ||
updated: Some(chrono::Utc::now()), | ||
deleted: true, | ||
}; | ||
|
||
let tag = Tag::update(&mut context.pool(), data.tag_id, &tag_form).await?; | ||
|
||
Ok(Json(CommunityTagResponse { | ||
id: tag.id, | ||
name: tag.name, | ||
community_id: tag.community_id, | ||
})) | ||
} | ||
|
||
#[tracing::instrument(skip(context))] | ||
pub async fn list_community_tags( | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data: Json<ListCommunityTags>, | ||
context: Data<LemmyContext>, | ||
) -> LemmyResult<Json<ListCommunityTagsResponse>> { | ||
let tags = Tag::get_by_community(&mut context.pool(), data.community_id).await?; | ||
|
||
let tag_responses = tags | ||
.into_iter() | ||
.map(|t| CommunityTagResponse { | ||
id: t.id, | ||
name: t.name, | ||
community_id: t.community_id, | ||
}) | ||
.collect(); | ||
|
||
Ok(Json(ListCommunityTagsResponse { | ||
tags: tag_responses, | ||
})) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ pub mod lock; | |
pub mod mark_many_read; | ||
pub mod mark_read; | ||
pub mod save; | ||
pub mod tags; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use activitypub_federation::config::Data; | ||
use actix_web::web::Json; | ||
use lemmy_api_common::{ | ||
context::LemmyContext, | ||
post::{UpdatePostTags, UpdatePostTagsResponse}, | ||
utils::check_community_mod_action, | ||
}; | ||
use lemmy_db_schema::{ | ||
source::{community::Community, post::Post, post_tag::PostTag, tag::PostTagInsertForm}, | ||
traits::Crud, | ||
}; | ||
use lemmy_db_views::structs::{LocalUserView, PostView}; | ||
use lemmy_utils::error::LemmyResult; | ||
|
||
#[tracing::instrument(skip(context))] | ||
pub async fn update_post_tags( | ||
data: Json<UpdatePostTags>, | ||
context: Data<LemmyContext>, | ||
local_user_view: LocalUserView, | ||
) -> LemmyResult<Json<UpdatePostTagsResponse>> { | ||
let post = Post::read(&mut context.pool(), data.post_id).await?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you fetch the PostView, then you won't need to also fetch the community below. |
||
let community = Community::read(&mut context.pool(), post.community_id).await?; | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let is_author = local_user_view.person.id == post.creator_id; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
|
||
if !is_author { | ||
// Check if user is either the post author or a community mod | ||
check_community_mod_action( | ||
&local_user_view.person, | ||
&community, | ||
false, | ||
&mut context.pool(), | ||
) | ||
.await?; | ||
} | ||
|
||
// Delete existing post tags | ||
PostTag::delete_for_post(&mut context.pool(), data.post_id).await?; | ||
|
||
// Create new post tags | ||
for tag_id in &data.tags { | ||
let form = PostTagInsertForm { | ||
post_id: data.post_id, | ||
tag_id: *tag_id, | ||
}; | ||
PostTag::create(&mut context.pool(), &form).await?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this need some validation? What if its a tag outside / not allowed by that community. If that's the case then you need to query community tags first and only allow the tag ids if it belongs. Also diesel lets you insert multiple forms at once, so you could create a new impl function on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, but if there is any invalid value just return an error directly. |
||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There seems to be two ways now that you're applying post tags, either in the post create itself, or via this method. I'm not sure if we should get rid of one of them. Seems like it might be cleaner to only keep this one, since it does the additional mod checks, and also clears out the other tags. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the author to change tags on his own post it makes sense to use the existing edit post endpoint. And then a separate endpoint for mods to change specific metadata on posts (eg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this request is to replace all tags of a post, while I left the "edit post" request to keep tags unchanged. On the "create post" request I did add tags directly.
Could you both discuss this and give me specific information on if / how you'd like to change this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is not a problem, use an option and if it is None leave the tags unchanged. So for own post the normal edit endpoint can be used.
Like I said a mod only endpoint to edit some post metadata makes sense, and you can allow changes to nsfw tag at the same time (#3566).
Replace all tags is fine, otherwise it gets too complicated. Mod only tags can also be done like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this approach also. As long as the EditPost and CreatePost are doing all the checks necessary. |
||
|
||
// Get updated post view | ||
let post_view = PostView::read( | ||
&mut context.pool(), | ||
data.post_id, | ||
Some(&local_user_view.local_user), | ||
false, | ||
) | ||
.await?; | ||
|
||
Ok(Json(UpdatePostTagsResponse { post_view })) | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,23 @@ use serde_with::skip_serializing_none; | |
#[cfg(feature = "full")] | ||
use ts_rs::TS; | ||
|
||
#[skip_serializing_none] | ||
#[derive(Debug, Serialize, Deserialize, Clone, Default)] | ||
#[cfg_attr(feature = "full", derive(TS))] | ||
#[cfg_attr(feature = "full", ts(export))] | ||
/// Update tags for a post. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would probably be good to add a comment about how either mods or post creators can do this. |
||
pub struct UpdatePostTags { | ||
pub post_id: PostId, | ||
pub tags: Vec<TagId>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
#[cfg_attr(feature = "full", derive(TS))] | ||
#[cfg_attr(feature = "full", ts(export))] | ||
pub struct UpdatePostTagsResponse { | ||
pub post_view: PostView, | ||
} | ||
phiresky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[skip_serializing_none] | ||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] | ||
#[cfg_attr(feature = "full", derive(TS))] | ||
|
@@ -178,8 +195,6 @@ pub struct EditPost { | |
/// Instead of fetching a thumbnail, use a custom one. | ||
#[cfg_attr(feature = "full", ts(optional))] | ||
pub custom_thumbnail: Option<String>, | ||
#[cfg_attr(feature = "full", ts(optional))] | ||
pub tags: Option<Vec<TagId>>, | ||
/// Time when this post should be scheduled. Null means publish immediately. | ||
#[cfg_attr(feature = "full", ts(optional))] | ||
pub scheduled_publish_time: Option<i64>, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would probably be better to split each action into its own file, like we've done with the other API actions.