From 99ff07b8afe424bc09273e2e374f1ab9a619e055 Mon Sep 17 00:00:00 2001 From: Philipp Herz Date: Sat, 25 Nov 2023 16:25:54 +0100 Subject: [PATCH] Implemented mod actions for posts --- .../controllers/CommentController.java | 16 ++- .../controllers/PostModActionsController.java | 104 ++++++++++++++++-- .../api/lemmy/v3/post/mappers/PostMapper.java | 8 +- .../sublinks/sublinksapi/post/dto/Post.java | 9 +- ...20231003__Create_initial_entity_tables.sql | 1 + 5 files changed, 114 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentController.java index fe241723..7f4b02a9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentController.java @@ -11,8 +11,6 @@ import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.EditComment; import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.GetComments; import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.GetCommentsResponse; -import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.ListCommentReports; -import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.ListCommentReportsResponse; import com.sublinks.sublinksapi.api.lemmy.v3.comment.models.MarkCommentReplyAsRead; import com.sublinks.sublinksapi.api.lemmy.v3.comment.services.LemmyCommentReportService; import com.sublinks.sublinksapi.api.lemmy.v3.comment.services.LemmyCommentService; @@ -23,7 +21,6 @@ import com.sublinks.sublinksapi.comment.dto.CommentReply; import com.sublinks.sublinksapi.comment.dto.CommentReport; import com.sublinks.sublinksapi.comment.enums.CommentSortType; -import com.sublinks.sublinksapi.comment.models.CommentReportSearchCriteria; import com.sublinks.sublinksapi.comment.models.CommentSearchCriteria; import com.sublinks.sublinksapi.comment.repositories.CommentReplyRepository; import com.sublinks.sublinksapi.comment.repositories.CommentReportRepository; @@ -33,11 +30,9 @@ import com.sublinks.sublinksapi.comment.services.CommentReplyService; import com.sublinks.sublinksapi.comment.services.CommentReportService; import com.sublinks.sublinksapi.comment.services.CommentService; -import com.sublinks.sublinksapi.community.dto.Community; import com.sublinks.sublinksapi.language.dto.Language; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.dto.Person; -import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.enums.ListingType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import com.sublinks.sublinksapi.person.services.PersonService; @@ -101,6 +96,15 @@ public CommentResponse create(@Valid @RequestBody final CreateComment createComm final Person person = getPersonOrThrowUnauthorized(principal); final Post post = postRepository.findById((long) createCommentForm.post_id()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + + if (post.isLocked()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_locked"); + } + + if (post.isDeleted() || post.isRemoved()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found"); + } + // Language Optional language; if (createCommentForm.language_id() != null) { @@ -108,9 +112,11 @@ public CommentResponse create(@Valid @RequestBody final CreateComment createComm } else { language = personService.getPersonDefaultPostLanguage(person, post.getCommunity()); } + if (language.isEmpty()) { throw new RuntimeException("No language selected"); } + final Comment comment = Comment.builder().person(person).isLocal(true) .commentBody(createCommentForm.content()).activityPubId("").post(post) .community(post.getCommunity()).language(language.get()).build(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostModActionsController.java index 9ebeb4b4..eb878bc3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostModActionsController.java @@ -3,6 +3,9 @@ import com.sublinks.sublinksapi.api.lemmy.v3.authentication.JwtPerson; import com.sublinks.sublinksapi.api.lemmy.v3.common.controllers.AbstractLemmyApiController; import com.sublinks.sublinksapi.api.lemmy.v3.errorhandler.ApiError; +import com.sublinks.sublinksapi.api.lemmy.v3.modlog.models.ModLockPost; +import com.sublinks.sublinksapi.api.lemmy.v3.modlog.models.ModRemovePost; +import com.sublinks.sublinksapi.api.lemmy.v3.post.models.FeaturePost; import com.sublinks.sublinksapi.api.lemmy.v3.post.models.ListPostReports; import com.sublinks.sublinksapi.api.lemmy.v3.post.models.ListPostReportsResponse; import com.sublinks.sublinksapi.api.lemmy.v3.post.models.PostReportResponse; @@ -10,17 +13,20 @@ import com.sublinks.sublinksapi.api.lemmy.v3.post.models.PostResponse; import com.sublinks.sublinksapi.api.lemmy.v3.post.models.ResolvePostReport; import com.sublinks.sublinksapi.api.lemmy.v3.post.services.LemmyPostReportService; +import com.sublinks.sublinksapi.api.lemmy.v3.post.services.LemmyPostService; import com.sublinks.sublinksapi.authorization.services.AuthorizationService; import com.sublinks.sublinksapi.community.dto.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.person.dto.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import com.sublinks.sublinksapi.post.dto.Post; import com.sublinks.sublinksapi.post.dto.PostReport; import com.sublinks.sublinksapi.post.models.PostReportSearchCriteria; import com.sublinks.sublinksapi.post.repositories.PostReportRepository; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.post.services.PostReportService; +import com.sublinks.sublinksapi.post.services.PostService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -28,10 +34,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import java.util.ArrayList; import java.util.List; - import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -58,6 +62,8 @@ public class PostModActionsController extends AbstractLemmyApiController { private final LinkPersonCommunityService linkPersonCommunityService; private final CommunityRepository communityRepository; private final PostRepository postRepository; + private final LemmyPostService lemmyPostService; + private final PostService postService; @Operation(summary = "A moderator remove for a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { @@ -65,20 +71,67 @@ public class PostModActionsController extends AbstractLemmyApiController { @ApiResponse(responseCode = "400", description = "Post Not Found", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PostMapping("remove") - PostResponse remove() { + PostResponse remove(@Valid @RequestBody final ModRemovePost modRemovePostForm, + final JwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + final Post post = postRepository.findById(modRemovePostForm.post_id()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + final boolean isAdmin = authorizationService.isAdmin(person); + + if (!isAdmin) { + final boolean moderatesCommunity = + linkPersonCommunityService.hasLink(person, post.getCommunity(), + LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, + post.getCommunity(), LinkPersonCommunityType.owner); - throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED); + if (!moderatesCommunity) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + } + } + + //@todo: Add it to the modlog + post.setRemoved(modRemovePostForm.removed()); + + return PostResponse.builder().post_view(lemmyPostService.postViewFromPost(post)).build(); } @Operation(summary = "A moderator lock for a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), @ApiResponse(responseCode = "400", description = "Post Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class))}), + @ApiResponse(responseCode = "403", description = "Forbidden", content = { + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class))})}) @PostMapping("lock") - PostResponse lock() { + PostResponse lock(@Valid @RequestBody final ModLockPost modLockPostForm, JwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + final Post post = postRepository.findById(modLockPostForm.post_id()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + final boolean isAdmin = authorizationService.isAdmin(person); - throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED); + if (!isAdmin) { + final boolean moderatesCommunity = + linkPersonCommunityService.hasLink(person, post.getCommunity(), + LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, + post.getCommunity(), LinkPersonCommunityType.owner); + + if (!moderatesCommunity) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + } + } + + //@todo: Add it to the modlog + post.setLocked(modLockPostForm.locked()); + System.out.println("post: " + post.isLocked()); + postService.updatePost(post); + + return PostResponse.builder().post_view(lemmyPostService.postViewFromPost(post)).build(); } @Operation(summary = "A moderator feature for a post (Sticky).") @@ -87,9 +140,42 @@ PostResponse lock() { @ApiResponse(responseCode = "400", description = "Post Not Found", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PostMapping("feature") - PostResponse feature() { + PostResponse feature(@Valid @RequestBody FeaturePost featurePostForm, JwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + final Post post = postRepository.findById((long) featurePostForm.post_id()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + final boolean isAdmin = authorizationService.isAdmin(person); + + if (!isAdmin) { + final boolean moderatesCommunity = + linkPersonCommunityService.hasLink(person, post.getCommunity(), + LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, + post.getCommunity(), LinkPersonCommunityType.owner); + + if (!moderatesCommunity) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + } + } + switch (featurePostForm.feature_type()) { + case Community: + post.setFeaturedInCommunity(featurePostForm.featured()); + break; + case Local: + if (!isAdmin) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_admin"); + } + post.setFeatured(featurePostForm.featured()); + break; + default: + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid_feature_type"); + } + + //@todo: Add it to the modlog - throw new ResponseStatusException(HttpStatus.NOT_IMPLEMENTED); + return PostResponse.builder().post_view(lemmyPostService.postViewFromPost(post)).build(); } @Operation(summary = "Resolve a post report. Only a mod can do this.") diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/mappers/PostMapper.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/mappers/PostMapper.java index d44f1e15..21e1107a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/mappers/PostMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/mappers/PostMapper.java @@ -14,10 +14,9 @@ public interface PostMapper extends Converter postLikes; @ManyToOne - @JoinTable( - name = "post_post_cross_post", - joinColumns = @JoinColumn(name = "post_id"), - inverseJoinColumns = @JoinColumn(name = "cross_post_id") - ) + @JoinTable(name = "post_post_cross_post", joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "cross_post_id")) CrossPost crossPost; /** @@ -95,6 +91,9 @@ public class Post implements AuthorizationEntityInterface { @Column(nullable = false, name = "is_local") private boolean isLocal; + @Column(nullable = false, name = "is_locked") + private boolean isLocked; + @Column(nullable = false, name = "is_featured") private boolean isFeatured; diff --git a/src/main/resources/db/migration/V20231003__Create_initial_entity_tables.sql b/src/main/resources/db/migration/V20231003__Create_initial_entity_tables.sql index 8d5fff01..39f68933 100644 --- a/src/main/resources/db/migration/V20231003__Create_initial_entity_tables.sql +++ b/src/main/resources/db/migration/V20231003__Create_initial_entity_tables.sql @@ -271,6 +271,7 @@ CREATE TABLE `posts` `is_deleted` TINYINT NOT NULL DEFAULT 0, `is_removed` TINYINT NOT NULL DEFAULT 0, `is_local` TINYINT NOT NULL DEFAULT 0, + `is_locked` TINYINT NOT NULL DEFAULT 0, `community_id` BIGINT NOT NULL, `is_featured` TINYINT NOT NULL DEFAULT 0, `is_featured_in_community` TINYINT NOT NULL DEFAULT 0,